In Projects

Factory Game

Factory is a game created in Python. The objective of the game is to create increasingly complicated assembly lines in a factory in order to build & sell increasingly complicated products.

Python Code - factory.py

1    # --- Program Overview: --- #
2    # Grid origin is bottom left corner, Scene origin is top left corner
3    # Each tile fits one machine and is made up of 25x25 pixels.
4    # List of Machines, Materials, Tiles.
5    # Coreloop loop start - Loop thru each machine to check for actions
6    #    Material instances created and appended to Materials list as needed
7    #    Loop thru each material to check for location relative to machines and tiles
8    # Buttons and menu responses are programmed as interrupts
9    # Images are either QGraphicsItems or QPixmaps and are created when object is created and moved around
10   
11   # --- Layout Structure: --- #
12   # QApplication Start
13   # QMainWindow (QMainWindow)
14   #    MainWidget (Widget) [Central Widget] - [mainVLayout (VBox Layout)]
15   #       upperDockWidget (Widget)
16   #       container (Widget) - [containerGrid (Grid Layout)]
17   #           viewFrame (QFrame)
18   #           buildMenuFrame (QFrame)
19   #           blueprintsMenuFrame (QFrame)
20   #           etc...
21   #       lowerDockWidget (Widget)
22   
23   # --- Development Notes: --- #
24   # Note that cannot append or delete objects from Machines, Materials, or Tiles mid iteration or else the
25   # for item in X list loops will not work properly. Must queue appends and deletes in separate lists then process at
26   # the end of the iteration. This also makes sense from a process perspective so that for example a material doesnt
27   # move into a splitter, then split, append new materials pieces to the list, then all those materials would be processed
28   # by the same splitter they came out of since they were added to the end of materials list that is in middle of
29   # processing. Similar story with Starters and double processing material in one iteration. Mostly to prevent bugs though.
30   # May have follow up bugs due to similar style interrupts placing a machine in the middle of code that processes append
31   # machine queue and clear machine queue. May need to enforce that entire block to run un-interrupted to prevent rare
32   # bugs.
33   
34   # --- Install Notes: --- #
35   # Install Pillow with 'cmd' > 'python -m pip install Pillow'
36   # Install win32api with 'cmd' > 'python -m pip install pypiwin32'
37   # Change python.exe and pythonw.exe compatibility settings to change DPI scaling set by Application in venv or sys.
38   
39   # -------- To Do -------- #
40   # setContentsMargins(left, top, right, bottom)
41   
42   # -------- Imports -------- #
43   from PyQt5 import QtCore, QtWidgets, QtGui
44   import datetime
45   import math
46   import pickle
47   from assyLineLib import *  # Usage: mainApp.lib.machineLib[STARTER]
48   from win32api import GetSystemMetrics  # Only used to detect monitor setup
49   import sys
50   import traceback
51   import random
52   import statistics
53   from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
54   import matplotlib.pyplot as plt
55   
56   # -------- Constants -------- #
57   
58   CYCLE_INTERVAL = 25  # Core loop time (ms) 25 = 1 roller/sec, 40 = 25fps, 17 = 60fps
59   MAT_LAUNCH_INTERVAL = 40  # Iterations between material launches
60   INCOME_ANALYSIS_FREQ = 10
61   FRAME_RATE_ANALYSIS_FREQ = 16
62   GRID_SIZE = 25
63   MACHINE_SIZE = 24
64   MAT_SIZE = 8
65   ORIENTATIONS = ['U', 'L', 'D', 'R']
66   ANGLE = {'U': 180, 'L': 90, 'D': 0, 'R': 270}
67   VISUAL_OFFSET_1_TO_2 = [(0, 0), (2, 0)]  # Visual offsets for groups of size 1 to 2 materials
68   VISUAL_OFFSET_3_TO_3 = [(-2, 0), (0, 2), (2, 0)]
69   VISUAL_OFFSET_4_TO_4 = [(-2, 2), (2, 2), (-2, -2), (2, -2)]
70   VISUAL_OFFSET_5_TO_9 = [(0, 0), (2, 0), (-2, 0),
71                           (0, -2), (2, -2), (-2, -2),
72                           (0, 2), (2, 2), (-2, 2)]
73   RESET_QUEUED = True
74   RESET_NOT_QUEUED = False
75   IN = 'In'
76   OUT = 'Out'
77   LEFT = 'Left'
78   RIGHT = 'Right'
79   RESET = 'Reset'
80   WALLED = True
81   NOT_WALLED = False
82   LOCKED = True
83   NOT_LOCKED = False
84   LOCK = 'Lock'  # drawShape argument flag
85   WALL = 'Wall'
86   HIGHLIGHT = 'Highlight'
87   Z_MACHINE_BOTTOM = 0  # Z Height Stack Order
88   Z_MATERIAL = 1
89   Z_MACHINE_TOP = 2
90   Z_ARROW = 3
91   Z_HIGHLIGHT = 3
92   STARTER = 'Starter'
93   CRAFTER = 'Crafter'
94   SELLER = 'Seller'
95   ROLLER = 'Roller'
96   DRAWER = 'Drawer'
97   CUTTER = 'Cutter'
98   FURNACE = 'Furnace'
99   PRESS = 'Press'
100  SPLITTER_LEFT = 'Splitter Left'
101  SPLITTER_RIGHT = 'Splitter Right'
102  SPLITTER_TEE = 'Splitter Tee'
103  SPLITTER_3WAY = 'Splitter 3-Way'
104  FILTER_LEFT = 'Filter Left'
105  FILTER_RIGHT = 'Filter Right'
106  FILTER_TEE = 'Filter Tee'
107  ROBOTIC_ARM = 'Robotic Arm'
108  FILTERED_ARM = 'Filtered Arm'
109  TELEPORTER_INPUT = 'Teleporter Input'
110  TELEPORTER_OUTPUT = 'Teleporter Output'
111  
112  
113  # -------- Classes -------- #
114  
115  
116  class QLabelT(QtWidgets.QLabel):  # Black BG, Larger Font
117      def __init__(self, parent=None):
118          super(QLabelT, self).__init__(parent)
119          font = QtGui.QFont()
120          font.setPointSize(16)
121          self.setFont(font)
122          self.setMinimumHeight(60)
123          self.setAlignment(QtCore.Qt.AlignCenter)
124          self.setStyleSheet('background:rgb(243, 243, 243);\ 
125                             color: black;\ 
126                             border: 0px solid black')
127  
128  
129  class QLabelImgA(QtWidgets.QLabel):
130      def __init__(self, parent=None, pixmap=None, styleSet=None, fixedWidth=None, fontSize=11):
131          super(QLabelImgA, self).__init__(parent)
132          font = QtGui.QFont()
133          font.setPointSize(11)
134          self.setFont(font)
135          if fixedWidth is not None:
136              self.setFixedWidth(fixedWidth)
137          self.setAlignment(QtCore.Qt.AlignCenter)
138          self.setPixmap(pixmap)
139          if styleSet == 'White-Square':  # White BG, Square Border
140              self.setStyleSheet('border: 1px solid black;\ 
141                                 background-color : white;\ 
142                                 color : black;\ 
143                                 padding: 2px')
144          elif styleSet == 'Blue-None':  # Blue BG, No Border
145              self.setStyleSheet('border: 0px solid black;\ 
146                                 background-color: steelblue;\ 
147                                 color: black;\ 
148                                 padding: 2px')
149          elif styleSet == 'White-None':  # White BG, No Border
150              self.setStyleSheet('border: 0px solid black;\ 
151                                 background-color: white;\ 
152                                 color: black;\ 
153                                 padding: 2px')
154          elif styleSet == 'Gray-None':  # Gray BG, No Border
155              self.setStyleSheet('border: 0px solid black;\ 
156                                 background-color: lightgray;\ 
157                                 color: black;\ 
158                                 padding: 2px')
159  
160  
161  class QLabelA(QtWidgets.QLabel):
162      def __init__(self, parent=None, styleSet='White-Square', fixedWidth=None, fontSize=11):
163          super(QLabelA, self).__init__(parent)
164          font = QtGui.QFont()
165          font.setPointSize(fontSize)
166          self.setFont(font)
167          self.setMinimumHeight(24)
168          if fixedWidth is not None:
169              self.setFixedWidth(fixedWidth)
170          self.setAlignment(QtCore.Qt.AlignCenter)
171          self.setStyleCode(styleSet)
172  
173      def setStyleCode(self, option):
174          if option == 'White-Square':  # White BG, Square Border
175              self.setStyleSheet('border: 1px solid black;\ 
176                                 background-color: white;\ 
177                                 color: black;\ 
178                                 padding: 2px')
179          elif option == 'White-Square-Table':  # White BG, Square Bottom Border
180              self.setStyleSheet('border-bottom: 1px solid black;\ 
181                                  background-color: white;\ 
182                                  color: black;\ 
183                                  padding: 2px')
184          elif option == 'White-Square-Table-Title':  # White BG, Square Top & Bottom Border
185              self.setStyleSheet('border-top: 1px solid black;\ 
186                                  border-bottom: 1px solid black;\ 
187                                  background-color: white;\ 
188                                  color: black;\ 
189                                  font-weight: bold;\ 
190                                  padding: 2px')
191          elif option == 'Gray-Square':  # Gray BG, Square Border
192              self.setStyleSheet('border: 1px solid black;\ 
193                                 background-color: lightgray;\ 
194                                 color: black;\ 
195                                 padding: 2px')
196          elif option == 'Light-Gray-Square':  # Light Gray BG, Square Border
197              self.setStyleSheet('border: 1px solid black;\ 
198                                 background-color: rgb(243, 243, 243);\ 
199                                 color: black;\ 
200                                 padding: 2px')
201          elif option == 'Green-Square':  # Green BG, White FG, Square Border
202              self.setStyleSheet('border: 1px solid black;\ 
203                                 background-color: green;\ 
204                                 color: white;\ 
205                                 padding: 2px')
206          elif option == 'Red-Square':  # Red BG, Square Border
207              self.setStyleSheet('border: 1px solid black;\ 
208                                 background-color: tomato;\ 
209                                 color: black;\ 
210                                 padding: 2px')
211          elif option == 'Blue-None':  # Blue BG, No Border
212              self.setStyleSheet('border: 0px solid black;\ 
213                                 background-color: steelblue;\ 
214                                 color: white;\ 
215                                 padding: 2px')
216          elif option == 'Gray-None':  # Gray BG, No Border
217              self.setStyleSheet('border: 0px solid black;\ 
218                                     background-color: lightgray;\ 
219                                     color: black;\ 
220                                     padding: 2px')
221          elif option == 'Blue-None-White-Large':  # Blue BG, No Border, White Text Slightly Larger
222              self.setStyleSheet('border: 0px solid black;\ 
223                                 background-color: steelblue;\ 
224                                 color: white;\ 
225                                 font-size: 24pt;\ 
226                                 padding: 2px')
227          elif option == 'Green-None':  # Green BG, White FG, No Border
228              self.setStyleSheet('border: 0px solid black;\ 
229                                 background-color: green;\ 
230                                 color: white;\ 
231                                 padding: 2px')
232          elif option == 'White-None':  # White BG, No Border
233              self.setStyleSheet('border: 0px solid black;\ 
234                                 background-color: white;\ 
235                                 color: black;\ 
236                                 padding: 2px')
237          elif option == 'White-Round':  # White BG, Rounded Border
238              self.setStyleSheet('border: 1px solid black;\ 
239                                 border-radius: 6;\ 
240                                 background-color: white;\ 
241                                 color: black;\ 
242                                 padding: 2px')
243          elif option == 'Green-Round':  # Green BG, White FG, Rounded Border
244              self.setStyleSheet('border: 1px solid black;\ 
245                                 border-radius: 6;\ 
246                                 background-color: green;\ 
247                                 color: white;\ 
248                                 padding: 2px')
249          elif option == 'Red-Round':  # Red BG, Rounded Border
250              self.setStyleSheet('border: 1px solid black;\ 
251                                 border-radius: 6;\ 
252                                 background-color: tomato;\ 
253                                 color: black;\ 
254                                 padding: 2px')
255          elif option == 'Blue-Round':  # Blue BG, Rounded Border
256              self.setStyleSheet('border: 1px solid black;\ 
257                                 border-radius: 6;\ 
258                                 background-color: steelblue;\ 
259                                 color: black;\ 
260                                 padding: 2px')
261          elif option == 'LightGray-Round':  # LightGray BG, Rounded Border
262              self.setStyleSheet('border: 1px solid black;\ 
263                                 border-radius: 6;\ 
264                                 background:rgb(243, 243, 243);\ 
265                                 color: black;\ 
266                                 padding: 2px')
267  
268  
269  class QPushButtonA(QtWidgets.QPushButton):
270      def __init__(self, parent=None, styleSet='White-Square', fixedWidth=None, fontSize=11):
271          super(QPushButtonA, self).__init__(parent)
272          font = QtGui.QFont()
273          font.setPointSize(fontSize)
274          self.setFont(font)
275          self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
276          self.setMinimumHeight(24)
277          if fixedWidth is not None:
278              self.setFixedWidth(fixedWidth)
279          self.setStyleCode(styleSet)
280  
281      def setStyleCode(self, option):
282          if option == 'OS':  # Gray OS Default
283              pass
284          elif option == 'White-Square-Menu-Left-Side':  # White BG, Square Partial Border For Main Menu Buttons
285              self.setStyleSheet('QPushButton{\ 
286                                  border-top: 1px solid black;\ 
287                                  border-left: 1px solid black;\ 
288                                  background-color: white;\ 
289                                  color: black;\ 
290                                  padding: 2px}\ 
291                                  QPushButton:hover{\ 
292                                  background-color: lightgray}')
293          elif option == 'White-Square-Menu-Right-Side':  # White BG, Square Partial Border For Main Menu Buttons
294              self.setStyleSheet('QPushButton{\ 
295                                  border-top: 1px solid black;\ 
296                                  border-left: 1px solid black;\ 
297                                  border-right: 1px solid black;\ 
298                                  background-color: white;\ 
299                                  color: black;\ 
300                                  padding: 2px}\ 
301                                  QPushButton:hover{\ 
302                                  background-color: lightgray}')
303          elif option == 'White-Square-Menu-Bottom-Side':  # White BG, Square Partial Border For Main Menu Buttons
304              self.setStyleSheet('QPushButton{\ 
305                                  border-top: 1px solid black;\ 
306                                  border-left: 1px solid black;\ 
307                                  border-bottom: 1px solid black;\ 
308                                  background-color: white;\ 
309                                  color: black;\ 
310                                  padding: 2px}\ 
311                                  QPushButton:hover{\ 
312                                  background-color: lightgray}')
313          elif option == 'Blue-Square-Menu-Left-Side':  # Blue BG, Square Partial Border For Main Menu Buttons
314              self.setStyleSheet('QPushButton{\ 
315                                  border-top: 1px solid black;\ 
316                                  border-left: 1px solid black;\ 
317                                  background-color: steelblue;\ 
318                                  color: white;\ 
319                                  padding: 2px}\ 
320                                  QPushButton:hover{\ 
321                                  background-color: steelblue}')
322          elif option == 'Blue-Square-Menu-Right-Side':  # Blue BG, Square Partial Border For Main Menu Buttons
323              self.setStyleSheet('QPushButton{\ 
324                                  border-top: 1px solid black;\ 
325                                  border-left: 1px solid black;\ 
326                                  border-right: 1px solid black;\ 
327                                  background-color: steelblue;\ 
328                                  color: white;\ 
329                                  padding: 2px}\ 
330                                  QPushButton:hover{\ 
331                                  background-color: steelblue}')
332          elif option == 'Blue-Square-Menu-Bottom-Side':  # Blue BG, Square Partial Border For Main Menu Buttons
333              self.setStyleSheet('QPushButton{\ 
334                                  border-top: 1px solid black;\ 
335                                  border-left: 1px solid black;\ 
336                                  border-bottom: 1px solid black;\ 
337                                  background-color: steelblue;\ 
338                                  color: white;\ 
339                                  padding: 2px}\ 
340                                  QPushButton:hover{\ 
341                                  background-color: steelblue}')
342          elif option == 'White-Square-Table':  # White BG, Square Bottom Border For Tables
343              self.setStyleSheet('QPushButton{\ 
344                                  border-top: 0px solid black;\ 
345                                  border-left: 0px solid black;\ 
346                                  border-right: 0px solid black;\ 
347                                  border-bottom: 1px solid black;\ 
348                                  background-color: white;\ 
349                                  color: black;\ 
350                                  padding: 2px}\ 
351                                  QPushButton:hover{\ 
352                                  background-color: steelblue;\ 
353                                  color: white}\ 
354                                  QPushButton:pressed{\ 
355                                  background-color: dodgerblue;\ 
356                                  color: white}')
357          elif option == 'White-Square':  # White BG, Square Border
358              self.setStyleSheet('QPushButton{\ 
359                                      border: 1px solid black;\ 
360                                      background-color: white;\ 
361                                      color: black;\ 
362                                      padding: 2px}\ 
363                                      QPushButton:hover{\ 
364                                      background-color: lightgray}\ 
365                                      QPushButton:pressed{\ 
366                                      border: 1px solid steelblue}')
367          elif option == 'Gray-Square':  # Gray BG, Square Border
368              self.setStyleSheet('QPushButton{\ 
369                                  border: 1px solid black;\ 
370                                  background-color: lightgray;\ 
371                                  color: black;\ 
372                                  padding: 2px}\ 
373                                  QPushButton:hover{\ 
374                                  border: 2px solid white}\ 
375                                  QPushButton:pressed{\ 
376                                  border: 3px solid white}')
377          elif option == 'Light-Gray-Square':  # Light Gray BG, Square Border
378              self.setStyleSheet('QPushButton{\ 
379                                  border: 1px solid black;\ 
380                                  background-color: rgb(243, 243, 243);\ 
381                                  color: black;\ 
382                                  padding: 2px}\ 
383                                  QPushButton:hover{\ 
384                                  border: 2px solid white}\ 
385                                  QPushButton:pressed{\ 
386                                  border: 3px solid white}')
387          elif option == 'Blue-Square':  # Blue BG, Square Border
388              self.setStyleSheet('QPushButton{\ 
389                                  border: 1px solid black;\ 
390                                  background-color: steelblue;\ 
391                                  color: white;\ 
392                                  padding: 2px}\ 
393                                  QPushButton:hover{\ 
394                                  border: 1px solid dodgerblue}\ 
395                                  QPushButton:pressed{\ 
396                                  border: 1px solid steelblue}')
397          elif option == 'White-Round':  # White BG, Rounded Border
398              self.setStyleSheet('QPushButton{\ 
399                                  border: 1px solid black;\ 
400                                  border-radius: 6;\ 
401                                  background-color: white;\ 
402                                  color: black;\ 
403                                  padding: 2px}\ 
404                                  QPushButton:hover{\ 
405                                  background-color: lightgray}\ 
406                                  QPushButton:pressed{\ 
407                                  border: 1px solid steelblue}')
408          elif option == 'White-None':  # White BG, No Border
409              self.setStyleSheet('QPushButton{\ 
410                                  border: 0px solid black;\ 
411                                  background-color: white;\ 
412                                  color: black;\ 
413                                  padding: 2px}\ 
414                                  QPushButton:hover{\ 
415                                  border: 1px solid dodgerblue}\ 
416                                  QPushButton:pressed{\ 
417                                  border: 1px solid steelblue}')
418          elif option == 'Blue-Round':  # Blue BG, Rounded Border
419              self.setStyleSheet('QPushButton{\ 
420                                  border: 1px solid black;\ 
421                                  border-radius: 6;\ 
422                                  background-color: steelblue;\ 
423                                  color: rgb(230, 230, 230);\ 
424                                  padding: 2px}\ 
425                                  QPushButton:hover{\ 
426                                  border: 1px solid dodgerblue}\ 
427                                  QPushButton:pressed{\ 
428                                  border: 1px solid steelblue}')
429          elif option == 'Green-Round':  # Green BG, Rounded Border
430              self.setStyleSheet('border: 1px solid black;\ 
431                                  border-radius: 6;\ 
432                                  background-color: green;\ 
433                                  color: white;\ 
434                                  padding: 2px}\ 
435                                  QPushButton:hover{\ 
436                                  border: 1px solid dodgerblue}\ 
437                                  QPushButton:pressed{\ 
438                                  border: 1px solid steelblue}')
439          elif option == 'Red-Round':  # Red BG, Rounded Border
440              self.setStyleSheet('QPushButton{\ 
441                                  border: 1px solid black;\ 
442                                  border-radius: 6;\ 
443                                  background-color: tomato;\ 
444                                  color: black;\ 
445                                  padding: 2px}\ 
446                                  QPushButton:hover{\ 
447                                  border: 1px solid dodgerblue}\ 
448                                  QPushButton:pressed{\ 
449                                  border: 1px solid steelblue}')
450          elif option == 'LightGray-Round':  # Light Gray BG, Rounded Border
451              self.setStyleSheet('QPushButton{\ 
452                                  border: 1px solid black;\ 
453                                  border-radius: 6;\ 
454                                  background:rgb(243, 243, 243);\ 
455                                  color: black;\ 
456                                  padding: 2px}\ 
457                                  QPushButton:hover{\ 
458                                  border: 1px solid dodgerblue}\ 
459                                  QPushButton:pressed{\ 
460                                  border: 1px solid steelblue}')
461          elif option == 'MediumGray-Round':  # Medium Gray BG, Rounded Border
462              self.setStyleSheet('QPushButton{\ 
463                                  border: 1px solid black;\ 
464                                  border-radius: 6;\ 
465                                  background:rgb(213, 213, 213);\ 
466                                  color: black;\ 
467                                  padding: 2px}\ 
468                                  QPushButton:hover{\ 
469                                  border: 1px solid dodgerblue}\ 
470                                  QPushButton:pressed{\ 
471                                  border: 1px solid steelblue}')
472  
473  
474  class clsMachine:
475      def __init__(self, main, machine, x, y, orientation, selectedBlueprint, starterQuantity=1, filterLeft=None,
476                   filterRight=None, teleportID=1):
477          # Machine variables
478          self.main = main
479          self.type = machine  # Machine type
480          self.x = x  # Machine position
481          self.y = y  # Machine position
482          self.orientation = orientation  # Machine orientation
483          self.cost = self.main.machineLib.lib[self.type]['buildCost']  # Tool cost
484          self.value = int(self.main.machineLib.lib[self.type]['buildCost'] / 4)  # Sell back value is 25% of cost
485          self.op_cost = self.main.machineLib.lib[self.type]['opCost']  # Tool operation cost
486          self.op_time = self.main.machineLib.lib[self.type]['opTime']  # Tool operation time
487          self.queueDelay = 0  # Delay to spawn queued material
488          self.queueMaterial = None  # Queued material
489          self.assyLine = self.getAssyLineNumber()  # Assembly line number of tool
490          self.consideredBlueprints = []  # Blueprints considered
491          self.contains = {}  # Machine inventory
492          self.onFloor = False  # Flag - Material on floor
493          self.starterQuantity = starterQuantity  # Starter - Spawn quantity
494          self.selectedBlueprint = selectedBlueprint  # Starter, Crafter - Blueprint selected
495          self.splitOutput = [0, 0, 0]  # Splitter - Iteration split distribution
496          self.splitCumulative = [0, 0, 0]  # Splitter - Cumulative split counter
497          self.splitSetting = None   # Splitter - Split setting
498          self.splitTurn = 0  # Splitter - Split turn
499          self.filterLeft = filterLeft  # Filter - Material selection left
500          self.filterRight = filterRight  # Filter - Material selection right
501          self.motionInProgress = False  # Robotic Arm - Motion in progress flag
502          self.motionFrame = 1  # Robotic Arm - Current motion frame number
503          self.returnMotion = False  # Robotic Arm - Motion direction flag
504          self.heldMaterial = None  # Robotic Arm - Held material object
505          self.xPickUpZone = None  # Robotic Arm - Material pick up zone x
506          self.yPickUpZone = None  # Robotic Arm - Material pick up zone y
507          self.xDropOffZone = None  # Robotic Arm - Material drop off zone x
508          self.yDropOffZone = None  # Robotic Arm - Material drop off zone y
509          self.xAbsLinkCenterAB = None  # Robotic Arm - Link Center
510          self.yAbsLinkCenterAB = None  # Robotic Arm - Link Center
511          self.xAbsLinkCenterBC = None  # Robotic Arm - Link Center
512          self.yAbsLinkCenterBC = None  # Robotic Arm - Link Center
513          self.teleporterID = teleportID  # Teleporter - ID number
514          self.teleporterActivated = False  # Teleporter - Activation (Unique ID Pairing)
515  
516          # Machine image and shape setup
517          self.shapeTop = None  # Shape Object - Top of Machine
518          self.shapeBottom = None  # Shape Object - Bottom of Machine
519          self.shapeImageTop = None  # Image Address - Top of Machine
520          self.shapeImageBottom = None  # Image Address - Bottom of Machine
521          self.shapeArm1 = None  # Shape Object - Robotic Arm Link 1
522          self.shapeArm2 = None  # Shape Object - Robotic Arm Link 2
523          self.xShape = None  # Machine Center x and y in scene coords
524          self.yShape = None  # Machine Center x and y in scene coords
525          self.arrow = None
526  
527          self.machineTypeSpecificSetup()
528  
529          self.drawShape()
530  
531      # -------- Machine Type Specific Setup -------- #
532  
533      def machineTypeSpecificSetup(self):
534          if self.type in [STARTER, CRAFTER]:  # Only consider selected blueprint by menu
535              if self.selectedBlueprint is not None:
536                  self.consideredBlueprints.append(self.selectedBlueprint)
537  
538          elif self.type in [DRAWER, CUTTER, FURNACE, PRESS]:  # Consider all its blueprints in its lib def
539              self.consideredBlueprints.extend(self.main.machineBlueprintList[self.type])  # Add pre-computed list
540  
541          elif self.type in [SELLER, ROLLER]:
542              pass
543  
544          elif self.type in [SPLITTER_LEFT, SPLITTER_RIGHT, SPLITTER_TEE, SPLITTER_3WAY]:
545              if self.type == SPLITTER_3WAY:  # <^>
546                  self.splitSetting = [1, 1, 1]  # Split settings - Relative Left, Straight, Right
547              elif self.type == SPLITTER_TEE:  # <_>
548                  self.splitSetting = [1, 0, 1]  # Split settings - Relative Left, Straight, Right
549              elif self.type == SPLITTER_LEFT:  # <^_
550                  self.splitSetting = [1, 1, 0]  # Split settings - Relative Left, Straight, Right
551              elif self.type == SPLITTER_RIGHT:  # _^>
552                  self.splitSetting = [0, 1, 1]  # Split settings - Relative Left, Straight, Right
553  
554          elif self.type in [FILTER_LEFT, FILTER_RIGHT, FILTER_TEE]:
555              pass
556  
557          elif self.type in [TELEPORTER_INPUT]:
558              pass
559  
560          elif self.type in [TELEPORTER_OUTPUT]:
561              pass
562  
563          elif self.type in [ROBOTIC_ARM]:
564              self.setPickupDropOffZones()
565              self.setUpdatedArmPositions()  # Update arm pos parameters
566  
567      # -------- Splitter Methods -------- #
568  
569      def splitMaterial(self, piece):
570          if self.splitSetting[self.splitTurn] == 0:  # Only 1 direction should be zero for any splitter
571              self.splitTurn = (self.splitTurn + 1) % 3
572  
573          self.splitOutput = [0, 0, 0]  # Initialize output queue to zeros [L, S, R]
574          for i in range(0, piece.quantity):
575              self.splitOutput[self.splitTurn] += 1  # Allocate 1 piece to output Queue in current direction
576              self.splitCumulative[self.splitTurn] += 1  # Allocate 1 piece to cumulative count in current direction
577              if self.splitCumulative[self.splitTurn] >= self.splitSetting[self.splitTurn]:  # Wait to hit cap then rotate
578                  self.splitCumulative[self.splitTurn] = 0
579                  self.splitTurn = (self.splitTurn + 1) % 3
580  
581          orientationIndex = ORIENTATIONS.index(self.orientation)  # [U, L, D, R]
582          if self.splitOutput[0] > 0:
583              self.main.Materials.append(clsMaterial(
584                  self.main, piece.type, self.x, self.y, ORIENTATIONS[(orientationIndex + 3) % 4],
585                  self.splitOutput[0]))  # Left
586          if self.splitOutput[1] > 0:
587              self.main.Materials.append(clsMaterial(
588                  self.main, piece.type, self.x, self.y, ORIENTATIONS[(orientationIndex + 0) % 4],
589                  self.splitOutput[1]))  # Straight
590          if self.splitOutput[2] > 0:
591              self.main.Materials.append(clsMaterial(
592                  self.main, piece.type, self.x, self.y, ORIENTATIONS[(orientationIndex + 1) % 4],
593                  self.splitOutput[2]))  # Right
594          piece.delMaterial()
595  
596      # -------- Filter Methods -------- #
597  
598      def filterMaterial(self, material):
599          orientationIndex = ORIENTATIONS.index(self.orientation)  # [U, L, D, R]
600          if material.type == self.filterLeft:
601              material.orientation = ORIENTATIONS[(orientationIndex + 3) % 4]  # Set to Left
602          elif material.type == self.filterRight:
603              material.orientation = ORIENTATIONS[(orientationIndex + 1) % 4]  # Set to Right
604  
605      # -------- Teleporter Methods -------- #
606  
607      def teleportMaterial(self, material):
608          for tool in self.main.Machines:
609              if tool.type == TELEPORTER_OUTPUT and tool.teleporterID == self.teleporterID:
610                  material.x = tool.x
611                  material.y = tool.y
612                  material.orientation = tool.orientation
613                  material.drawShape()
614                  return
615          self.main.updateMessage('Material destroyed by teleporter')
616          material.delMaterial()  # Del mat if no matching teleporter
617  
618      # -------- Robotic Arm Methods -------- #
619  
620      def setPickupDropOffZones(self):
621          if self.orientation == 'U':
622              self.xPickUpZone, self.yPickUpZone = self.x, self.y + GRID_SIZE
623              self.xDropOffZone, self.yDropOffZone = self.x, self.y - GRID_SIZE
624          elif self.orientation == 'D':
625              self.xPickUpZone, self.yPickUpZone = self.x, self.y - GRID_SIZE
626              self.xDropOffZone, self.yDropOffZone = self.x, self.y + GRID_SIZE
627          elif self.orientation == 'L':
628              self.xPickUpZone, self.yPickUpZone = self.x - GRID_SIZE, self.y
629              self.xDropOffZone, self.yDropOffZone = self.x + GRID_SIZE, self.y
630          elif self.orientation == 'R':
631              self.xPickUpZone, self.yPickUpZone = self.x + GRID_SIZE, self.y
632              self.xDropOffZone, self.yDropOffZone = self.x - GRID_SIZE, self.y
633  
634      def pickUpMaterial(self, material):
635          self.motionInProgress = True
636          self.motionFrame = 1
637          self.heldMaterial = material
638          self.heldMaterial.pickedUp = True
639  
640      def dropOffMaterial(self):
641          self.heldMaterial.x = self.xDropOffZone
642          self.heldMaterial.y = self.yDropOffZone - 1  # Drop material 1px below tile center so it moves to center next
643          self.heldMaterial.orientation = 'U'  # Set orientation to up so it moves onto tile center next
644          self.heldMaterial.drawShape()
645          self.heldMaterial.pickedUp = False
646          self.heldMaterial = None
647          self.returnMotion = True  # Trigger backwards motion animation
648  
649      def getAssyLineNumber(self):
650          if 0 <= self.x <= 16 * GRID_SIZE:  # Row 1 thru 16 valid
651              return 1
652          elif 17 * GRID_SIZE <= self.x <= 35 * GRID_SIZE:  # Row 18 thru 34 valid
653              return 2
654          elif 36 * GRID_SIZE <= self.x <= 53 * GRID_SIZE:  # Row 36 thru 52 valid
655              return 3
656  
657      def setSelectedBlueprint(self, material):  # Only for Starter and Crafter have blueprint select option
658          self.selectedBlueprint = material
659          self.consideredBlueprints.clear()
660          self.consideredBlueprints.append(self.selectedBlueprint)
661  
662      def addMaterialToInventory(self, material):
663          for i in range(material.quantity):  # Accounts for stacks of material
664              self.contains[material.type] = self.contains.get(material.type, 0) + 1  # Default to 0 if none then add 1
665  
666      def clearInventory(self):
667          self.contains.clear()
668  
669      def moveMachine(self, x, y):
670          self.x = x
671          self.y = y
672          self.drawShape()
673          if self.type in [ROBOTIC_ARM, FILTERED_ARM]:
674              self.motionInProgress = False
675              self.motionFrame = 1
676              self.setPickupDropOffZones()
677              self.setUpdatedArmPositions()  # Update arm pos parameters
678              if self.heldMaterial is not None:
679                  self.heldMaterial.delMaterial()
680                  self.heldMaterial = None
681  
682      def rotateMachine(self):
683          orientationIndex = ORIENTATIONS.index(self.orientation)
684          newOrientationIndex = (orientationIndex + 1) % 4  # [U, L, D, R]
685          self.orientation = ORIENTATIONS[newOrientationIndex]
686          self.setPickupDropOffZones()  # Update pickup/dropoff zone parameters
687          self.setUpdatedArmPositions()  # Update arm pos parameters
688          self.drawShape()
689          self.drawArrow()  # Redraw the arrow on top
690  
691      def sellMachine(self):
692          self.main.updateBalance(self.main.balance + self.value)
693          self.main.updateMessage('Sold Machine for $%s' % self.main.shortNum(self.value))
694          self.delMachine()
695  
696      def delMachine(self):
697          self.delArrow()
698          self.delShape()
699          self.main.Machines.remove(self)  # Don't del object, remove from list and let garb collect
700  
701      def drawShape(self):
702          pixmap = self.main.machineLib.lib[self.type]['imageBottom']
703          if self.shapeBottom is None:
704              self.shapeBottom = self.addShapeToScene(pixmap, Z_MACHINE_BOTTOM, rotate=True)
705          self.setShapePosAndPixmap(self.shapeBottom, self.x, self.y, pixmap)
706          self.shapeBottom.setRotation(ANGLE[self.orientation])
707  
708          pixmap = self.main.machineLib.lib[self.type]['imageTop']
709          if self.shapeTop is None:
710              self.shapeTop = self.addShapeToScene(pixmap, Z_MACHINE_TOP, rotate=True)
711          self.setShapePosAndPixmap(self.shapeTop, self.x, self.y, pixmap)
712          self.shapeTop.setRotation(ANGLE[self.orientation])
713  
714          if self.type in [ROBOTIC_ARM, FILTERED_ARM]:  # Arms shouldn't rotate real-time, setPixmap to rotated version
715              pixmap = self.main.angledPixmapLink1[self.main.thetaAB[self.orientation][self.motionFrame]]
716              if self.shapeArm1 is None:
717                  self.shapeArm1 = self.addShapeToScene(pixmap, Z_MACHINE_TOP, rotate=False)
718              self.setShapePosAndPixmap(self.shapeArm1, self.xAbsLinkCenterAB, self.yAbsLinkCenterAB, pixmap)
719  
720              pixmap = self.main.angledPixmapLink2[self.main.thetaBC[self.orientation][self.motionFrame]]
721              if self.shapeArm2 is None:
722                  self.shapeArm2 = self.addShapeToScene(pixmap, Z_MACHINE_TOP, rotate=False)
723              self.setShapePosAndPixmap(self.shapeArm2, self.xAbsLinkCenterBC, self.yAbsLinkCenterBC, pixmap)
724  
725      def addShapeToScene(self, pixmap, zValue, rotate):
726          newShape = self.main.scene.addPixmap(pixmap)
727          if rotate is True:
728              newShape.setTransformOriginPoint(MACHINE_SIZE / 2, MACHINE_SIZE / 2)  # Set rotation around pixmap center
729          newShape.setZValue(zValue)
730          return newShape
731  
732      def setShapePosAndPixmap(self, shape, x, y, pixmap):
733          xShape, yShape = self.main.convertToSceneCoords(x, y, pixmap.width(), pixmap.height())
734          shape.setPos(xShape, yShape)
735          shape.setPixmap(pixmap)
736  
737      def processArmMovement(self):
738          if self.motionInProgress is True:
739              # Display next gif frame
740              self.setUpdatedArmPositions()
741              self.setShapePosAndPixmap(self.shapeArm1, self.xAbsLinkCenterAB, self.yAbsLinkCenterAB,
742                                        self.main.angledPixmapLink1[
743                                            self.main.thetaAB[self.orientation][self.motionFrame]])
744              self.setShapePosAndPixmap(self.shapeArm2, self.xAbsLinkCenterBC, self.yAbsLinkCenterBC,
745                                        self.main.angledPixmapLink2[
746                                            self.main.thetaBC[self.orientation][self.motionFrame]])
747              self.moveMaterialHeldByArm()
748  
749              # Set material down and start return animation
750              if self.motionFrame == 48:  # Robotic Arm should have 48 frames
751                  self.dropOffMaterial()
752  
753              # Mark end of animation
754              if self.returnMotion is True and self.motionFrame == 1:
755                  self.motionInProgress = False
756                  self.returnMotion = False
757  
758              # Increment motionFrame frame counter
759              if self.motionInProgress is True and self.returnMotion is False:
760                  self.motionFrame += 1
761              elif self.motionInProgress is True and self.returnMotion is True:
762                  self.motionFrame -= 1
763  
764      def setUpdatedArmPositions(self):
765          self.xAbsLinkCenterAB = self.x + self.main.xRelLinkCenterAB[self.orientation][self.motionFrame]
766          self.yAbsLinkCenterAB = self.y + self.main.yRelLinkCenterAB[self.orientation][self.motionFrame]
767  
768          self.xAbsLinkCenterBC = self.x + self.main.xRelLinkCenterBC[self.orientation][self.motionFrame]
769          self.yAbsLinkCenterBC = self.y + self.main.yRelLinkCenterBC[self.orientation][self.motionFrame]
770  
771      def moveMaterialHeldByArm(self):
772          if self.heldMaterial is not None:
773              self.heldMaterial.move(self.x + self.main.xRelC[self.orientation][self.motionFrame],
774                                     self.y + self.main.yRelC[self.orientation][self.motionFrame])
775  
776      def delShape(self):
777          if self.shapeTop is not None:
778              self.main.scene.removeItem(self.shapeTop)
779              self.shapeTop = None
780          if self.shapeBottom is not None:
781              self.main.scene.removeItem(self.shapeBottom)
782              self.shapeBottom = None
783          if self.shapeArm1 is not None:
784              self.main.scene.removeItem(self.shapeArm1)
785              self.shapeArm1 = None
786          if self.shapeArm2 is not None:
787              self.main.scene.removeItem(self.shapeArm2)
788              self.shapeArm2 = None
789  
790      def drawArrow(self):
791          # Define arrow path in space
792          path = QtGui.QPainterPath()
793          path.moveTo(0, 8)  # Straight Line
794          path.lineTo(0, -8)
795          path.moveTo(0, 8)  # Diagonal to Left
796          path.lineTo(-6, 2)
797          path.moveTo(0, 8)  # Diagonal to Right
798          path.lineTo(6, 2)
799  
800          # Add arrow to scene, set position, set rotation
801          self.delArrow()
802          self.xShape, self.yShape = self.main.convertToSceneCoords(self.x, self.y, 0, 0)
803          self.arrow = self.main.scene.addPath(path, QtGui.QPen(QtCore.Qt.red, 2))
804          self.arrow.setPos(self.xShape, self.yShape)
805          self.arrow.setZValue(Z_ARROW)
806          self.arrow.setRotation(ANGLE[self.orientation])
807  
808      def delArrow(self):
809          if self.arrow is not None:
810              self.main.scene.removeItem(self.arrow)
811              self.arrow = None
812  
813  
814  class clsMaterial:
815      def __init__(self, main, materialType, x, y, orientation, quantity):
816          self.main = main
817          self.type = materialType
818          self.x = x  # Material x position
819          self.y = y  # Material y position
820          self.orientation = orientation  # Material orientation
821          self.quantity = quantity  # Quantity of material in stack (1, 2, or 3)
822          self.value = self.main.materialLib.lib[self.type]['value']  # Material sale price
823          self.pickedUp = False  # Flag material picked up by Robotic Arm
824          self.groupID = None  # Material group identification
825          self.groupPos = None  # Position in the material group
826          self.xVisOffset = 0  # Visual x offset on rollers
827          self.yVisOffset = 0  # Visual y offset on rollers
828  
829          # Material Shape Setup
830          self.xShape = None
831          self.yShape = None
832          self.shape = None
833          if self.quantity == 1:
834              self.image = self.main.materialLib.lib[self.type]['image']
835          elif self.quantity == 2:
836              self.image = self.main.materialLib.lib[self.type]['image_qty_2']
837          elif self.quantity == 3:
838              self.image = self.main.materialLib.lib[self.type]['image_qty_3']
839          self.shapeImage = None
840          self.drawShape()
841  
842      def rollerMove(self):  # Move forward 1px by orientation
843          i = ORIENTATIONS.index(self.orientation)  # [U, L, D, R]
844          xMovement = [0, -1, 0, 1]
845          yMovement = [1, 0, -1, 0]
846          self.x += xMovement[i]
847          self.y += yMovement[i]
848          self.xShape, self.yShape = self.main.convertToSceneCoords(self.x + self.xVisOffset, self.y + self.yVisOffset,
849                                                                    MAT_SIZE, MAT_SIZE)
850          self.shape.setPos(self.xShape, self.yShape)
851  
852      def move(self, xNew, yNew):  # Move material to given location
853          self.x = xNew
854          self.y = yNew
855          self.xShape, self.yShape = self.main.convertToSceneCoords(self.x + self.xVisOffset, self.y + self.yVisOffset,
856                                                                    MAT_SIZE, MAT_SIZE)
857          self.shape.setPos(self.xShape, self.yShape)
858  
859      def checkIfAtTileCenter(self):
860          # This method may or may not be faster. Need to do a speed test to find out if it is.
861          # This code runs once in main application
862          #   xTileCenters = range(13, self.main.sceneWidth, GRID_SIZE)
863          #   yTileCenters = range(13, self.main.sceneHeight, GRID_SIZE)
864          # This function changes to simple check if x and y are in the list of centers
865          #   if self.x in xTileCenters and self.y in yTileCenters
866          #       return True
867          #   else:
868          #       return False
869  
870          if (self.x - 13) % GRID_SIZE == 0 and (self.y - 13) % GRID_SIZE == 0:
871              return True
872          else:
873              return False
874  
875      def setGroupVisualOffset(self, offsetList):
876          self.xVisOffset, self.yVisOffset = offsetList[self.groupPos % 9]  # Mod 9 to restart positioning after 9
877  
878      def delMaterial(self):
879          if self.groupID is not None:
880              self.main.groupIDList[self.groupID].remove(self)  # Remove material from main.groupIDList
881          self.delShape()
882          self.main.Materials.remove(self)
883  
884      def drawShape(self):
885          self.delShape()
886          self.xShape, self.yShape = self.main.convertToSceneCoords(self.x + self.xVisOffset, self.y + self.yVisOffset,
887                                                                    self.image.width(), self.image.height())
888          self.shape = self.main.scene.addPixmap(self.image)
889          self.shape.setZValue(Z_MATERIAL)
890          self.shape.setPos(self.xShape, self.yShape)
891  
892      def delShape(self):
893          if self.shape is not None:
894              self.main.scene.removeItem(self.shape)
895              self.shape = None
896  
897  
898  class clsTile:
899      def __init__(self, main, x, y):
900          self.main = main
901          self.x = x
902          self.y = y
903          self.assyLine = self.getAssyLineNumber()
904          self.locked = False
905          self.walled = False
906          self.shape_highlight = None
907          self.shape_lock = None
908          self.shape_wall = None
909          self.xShape = None
910          self.yShape = None
911          self.xShape = self.x
912          self.yShape = self.main.sceneHeight - self.y  # Scene and material coord system differ
913  
914      def getAssyLineNumber(self):
915          if 0 <= self.x <= 16 * GRID_SIZE:  # Row 1 thru 16 valid
916              return 1
917          elif 17 * GRID_SIZE <= self.x <= 35 * GRID_SIZE:  # Row 18 thru 34 valid
918              return 2
919          elif 36 * GRID_SIZE <= self.x <= 53 * GRID_SIZE:  # Row 36 thru 52 valid
920              return 3
921  
922      def buyTile(self):
923          self.markAsUnlocked()
924          self.main.updateBalance(self.main.balance - self.main.getTilePrice())
925          self.main.updateMessage('Bought Tile for $%s!' % self.main.shortNum(self.main.getTilePrice()))
926          self.main.unlockedTiles.append((self.x, self.y))
927          self.main.statusBar.showMessage('Purchase a Tile for $%s' % self.main.shortNum(self.main.getTilePrice()))
928  
929      def markAsLocked(self):
930          self.drawShape(LOCK)
931          self.locked = True
932  
933      def markAsUnlocked(self):
934          self.delShape(LOCK)
935          self.locked = False
936  
937      def markAsWalled(self):
938          self.drawShape(WALL)
939          self.walled = True
940  
941      def markAsUnwalled(self):
942          self.delShape(WALL)
943          self.walled = False
944  
945      def markAsHighlighted(self):
946          self.drawShape(HIGHLIGHT)
947  
948      def markAsNotHighlighted(self):
949          self.delShape(HIGHLIGHT)
950  
951      def drawShape(self, shapeType):
952          self.delShape(shapeType)
953          if shapeType == HIGHLIGHT:
954              self.xShape, self.yShape = self.main.convertToSceneCoords(self.x, self.y, GRID_SIZE, GRID_SIZE)
955              self.shape_highlight = QtWidgets.QGraphicsRectItem(
956                  QtCore.QRectF(self.xShape, self.yShape, GRID_SIZE, GRID_SIZE))
957              self.shape_highlight.setZValue(Z_HIGHLIGHT)
958              self.shape_highlight.setPen(QtGui.QPen(QtCore.Qt.green, 3))
959              self.main.scene.addItem(self.shape_highlight)
960          elif shapeType == LOCK:
961              self.xShape, self.yShape = self.main.convertToSceneCoords(self.x, self.y, MACHINE_SIZE, MACHINE_SIZE)
962              self.shape_lock = QtWidgets.QGraphicsRectItem(
963                  QtCore.QRectF(self.xShape, self.yShape, MACHINE_SIZE, MACHINE_SIZE))
964              self.shape_lock.setZValue(Z_HIGHLIGHT)
965              self.shape_lock.setBrush(QtGui.QColor(200, 200, 200))
966              self.shape_lock.setPen(QtGui.QPen(QtCore.Qt.black, 1))
967              self.main.scene.addItem(self.shape_lock)
968          elif shapeType == WALL:
969              pixmap = self.main.imageLib.lib['Wall']['image']
970              self.xShape, self.yShape = self.main.convertToSceneCoords(self.x, self.y, pixmap.width(), pixmap.height())
971              self.shape_wall = self.main.scene.addPixmap(pixmap)
972              self.shape_wall.setZValue(Z_HIGHLIGHT)
973              self.shape_wall.setPos(self.xShape, self.yShape)
974  
975      def delShape(self, shapeType):
976          if shapeType == HIGHLIGHT:
977              if self.shape_highlight is not None:
978                  self.main.scene.removeItem(self.shape_highlight)
979                  self.shape_highlight = None
980          elif shapeType == LOCK:
981              if self.shape_lock is not None:
982                  self.main.scene.removeItem(self.shape_lock)
983                  self.shape_lock = None
984          elif shapeType == WALL:
985              if self.shape_wall is not None:
986                  self.main.scene.removeItem(self.shape_wall)
987                  self.shape_wall = None
988  
989  
990  class baseMenuFrame(QtWidgets.QFrame):
991      # Menu Frame GUI Setup
992      #   BaseMenuFrame (QFrame) [vbox (QVBox)]
993      #       topW
994      #       grid
995      #       bottomW
996  
997      def __init__(self, parent=None, main=None):
998          super(baseMenuFrame, self).__init__(parent)
999          self.parent = parent
1000         self.main = main
1001         self.selectedTool = None  # Selected tool for tool properties menu
1002         self.setMinimumWidth(480)
1003         self.setMinimumHeight(600)
1004         # self.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
1005 
1006         # Frame Setup
1007         self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
1008         self.setObjectName('baseMenuFrame')
1009         self.setStyleSheet('#baseMenuFrame{background-color: white;\ 
1010                            border: 1px solid black}')
1011 
1012         # Top Bar
1013         self.topW = QtWidgets.QFrame()
1014         topWLayout = QtWidgets.QVBoxLayout()
1015         self.topW.setMinimumHeight(65)
1016         self.topW.setObjectName('baseMenuFrameTop')
1017         self.topW.setStyleSheet('#baseMenuFrameTop{background:rgb(243, 243, 243);\ 
1018                                  border-top: 0px solid black;\ 
1019                                  border-left: 0px solid black;\ 
1020                                  border-right: 0px solid black;\ 
1021                                  border-bottom: 1px solid black;\ 
1022                                  color: black}')
1023         self.titleL = QLabelT('Title')
1024         topWLayout.setContentsMargins(0, 0, 0, 0)
1025         topWLayout.addWidget(self.titleL)
1026         self.topW.setLayout(topWLayout)
1027 
1028         # Bottom Bar
1029         self.bottomW = QtWidgets.QFrame()
1030         bottomWLayout = QtWidgets.QVBoxLayout()
1031         self.bottomW.setMinimumHeight(50)
1032         self.bottomW.setObjectName('baseMenuFrameBottom')
1033         self.bottomW.setStyleSheet('#baseMenuFrameBottom{background:rgb(243, 243, 243);\ 
1034                                     border-top: 1px solid black;\ 
1035                                     border-left: 0px solid black;\ 
1036                                     border-right: 0px solid black;\ 
1037                                     border-bottom: 0px solid black;\ 
1038                                     color: black}')
1039         self.cancelB = QPushButtonA('Cancel', 'White-Square', 200)
1040         self.cancelB.clicked.connect(self.cancelMenu)
1041         bottomWLayout.setAlignment(QtCore.Qt.AlignCenter)
1042         bottomWLayout.addWidget(self.cancelB)
1043         self.bottomW.setLayout(bottomWLayout)
1044 
1045         # Grid Contents - Subclass adds content to this layout
1046         self.contents = QtWidgets.QWidget()
1047 
1048         # Frame's Layout
1049         vbox = QtWidgets.QVBoxLayout()
1050         vbox.setContentsMargins(0, 0, 0, 0)
1051         vbox.addWidget(self.topW)
1052         vbox.addStretch(1)
1053         vbox.addWidget(self.contents)
1054         vbox.addStretch(1)
1055         vbox.addWidget(self.bottomW)
1056         self.setLayout(vbox)
1057 
1058     def cancelMenu(self):
1059         self.selectedTool = None
1060         self.main.raiseFrame(self.main.viewFrame)
1061 
1062 
1063 class scrollingBaseMenuFrame(QtWidgets.QFrame):
1064     # Scrolling Menu Frame GUI Setup
1065     #   scrollingBaseMenuFrame (QFrame) [QVBox]
1066     #       scrollArea (QScrollArea)
1067     #         scrollAreaWidgetContents (QWidget) [QVBox] (Scrolling)
1068     #           topW
1069     #           grid
1070     #   bottomW (Static)
1071 
1072     def __init__(self, parent=None, main=None):
1073         super(scrollingBaseMenuFrame, self).__init__(parent)
1074         self.parent = parent
1075         self.main = main
1076         self.selectedTool = None  # Selected tool for tool properties menu
1077         self.setMinimumWidth(480)
1078         self.setMinimumHeight(480)
1079         self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
1080 
1081         # Frame Setup
1082         self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
1083         self.setObjectName('baseMenuFrame')
1084         self.setStyleSheet('#baseMenuFrame{\ 
1085                             background-color: white;\ 
1086                             border: 1px solid black}')
1087 
1088         # Top Bar
1089         self.topW = QtWidgets.QFrame()
1090         topWLayout = QtWidgets.QVBoxLayout()
1091         self.topW.setMinimumHeight(65)
1092         self.topW.setObjectName('baseMenuFrameTop')
1093         self.topW.setStyleSheet('#baseMenuFrameTop{background:rgb(243, 243, 243);\ 
1094                                  border-top: 0px solid black;\ 
1095                                  border-left: 0px solid black;\ 
1096                                  border-right: 1px solid black;\ 
1097                                  border-bottom: 1px solid black;\ 
1098                                  color: black}')
1099         self.titleL = QLabelT('Title')
1100         topWLayout.setContentsMargins(0, 0, 0, 0)
1101         topWLayout.addWidget(self.titleL)
1102         self.topW.setLayout(topWLayout)
1103 
1104         # Bottom Bar
1105         self.bottomW = QtWidgets.QFrame()
1106         bottomWLayout = QtWidgets.QVBoxLayout()
1107         self.bottomW.setMinimumHeight(50)
1108         self.bottomW.setObjectName('baseMenuFrameBottom')
1109         self.bottomW.setStyleSheet('#baseMenuFrameBottom{\ 
1110                                     background:rgb(243, 243, 243);\ 
1111                                     border-top: 1px solid black;\ 
1112                                     border-left: 0px solid black;\ 
1113                                     border-right: 0px solid black;\ 
1114                                     border-bottom: 0px solid black;\ 
1115                                     color: black}')
1116         self.cancelB = QPushButtonA('Cancel', 'White-Square', 200)
1117         bottomWLayout.setAlignment(QtCore.Qt.AlignCenter)
1118         self.cancelB.clicked.connect(lambda: self.main.raiseFrame(self.main.viewFrame))
1119         bottomWLayout.addWidget(self.cancelB)
1120         self.bottomW.setLayout(bottomWLayout)
1121 
1122         # Grid Contents
1123         self.contents = QtWidgets.QWidget()
1124 
1125         # Scrolling Contents Setup
1126         self.scrollAreaWidgetContents = QtWidgets.QWidget()  # Scroll Area Contents Wid
1127         self.scrollAreaWidgetContents.setObjectName('baseMenuScrollingContents')
1128         self.scrollAreaWidgetContents.setStyleSheet('#baseMenuScrollingContents{\ 
1129                                                     background-color: white;\ 
1130                                                     border-top: 1px solid black;\ 
1131                                                     border-left: 0px solid black;\ 
1132                                                     border-right: 1px solid black;\ 
1133                                                     border-bottom: 0px solid black;}')
1134         vbox = QtWidgets.QVBoxLayout()  # Top level layout
1135         vbox.addWidget(self.topW)
1136         vbox.addWidget(self.contents)
1137         vbox.addStretch(1)
1138         vbox.setContentsMargins(0, 0, 0, 0)
1139         self.scrollAreaWidgetContents.setLayout(vbox)
1140 
1141         # Scrolling Layout
1142         self.scrollArea = QtWidgets.QScrollArea()  # Add ScrollArea to self
1143         self.scrollArea.setWidget(self.scrollAreaWidgetContents)  # Set Scroll Area to Contents Wid
1144         self.scrollArea.setWidgetResizable(True)
1145         layout = QtWidgets.QVBoxLayout()
1146         layout.setSpacing(0)
1147         layout.setContentsMargins(0, 0, 0, 0)
1148         layout.addWidget(self.scrollArea)
1149         layout.addWidget(self.bottomW)
1150         self.setLayout(layout)
1151 
1152 
1153 class toolPropertiesMenu(baseMenuFrame):
1154     def __init__(self, parent=None, main=None):
1155         super(toolPropertiesMenu, self).__init__(parent, main)
1156 
1157         # Set Menu Parameters
1158         self.setMaximumHeight(700)
1159         self.titleL.setText('None')
1160 
1161         # Set Contents
1162         self.vbox = QtWidgets.QVBoxLayout()
1163         self.vbox.setContentsMargins(0, 0, 0, 0)
1164 
1165         # Blueprint Layout
1166         self.blueprintWidget = QtWidgets.QFrame()
1167         self.blueprintWidget.setObjectName('blueprintWidget')
1168         self.blueprintWidget.setStyleSheet('#blueprintWidget{background-color: rgb(243, 243, 243);\ 
1169                                            margin:20px;\ 
1170                                            border:1px solid black;}')
1171         self.blueprintSubGrid = QtWidgets.QGridLayout()
1172         self.blueprintSubGrid.setSpacing(14)
1173         self.blueprintSubGrid.setContentsMargins(8, 8, 8, 8)
1174 
1175         self.curBlueprintL = QLabelA('Blueprint', 'Light-Gray-None', None, 13)
1176 
1177         self.itemFrame = QtWidgets.QFrame()
1178         self.itemFrame.setStyleSheet('QFrame{\ 
1179                                      background-color:\ 
1180                                      gray; border: 1px solid black}')
1181         self.itemGrid = QtWidgets.QGridLayout()
1182         self.itemGrid.setAlignment(QtCore.Qt.AlignLeft)
1183 
1184         self.blankImg = QtGui.QPixmap()
1185         self.baseName = QLabelA('', 'Blue-None', None, 11)
1186         self.baseImg = QLabelImgA('', self.blankImg, 'Blue-None')
1187         self.baseQty = QLabelA('', 'Blue-None', None, 11)
1188         self.baseName.setWordWrap(True)
1189 
1190         self.cellFrame = QtWidgets.QFrame()
1191         self.cellFrame.setStyleSheet('background-color: steelblue;\ 
1192                                       border-radius: 6')
1193         self.cellFrame.setFixedSize(100, 120)
1194         cellVBox = QtWidgets.QVBoxLayout()
1195         cellVBox.setContentsMargins(0, 0, 0, 0)
1196         cellVBox.addWidget(self.baseName)
1197         cellVBox.addWidget(self.baseImg)
1198         cellVBox.addWidget(self.baseQty)
1199         self.cellFrame.setLayout(cellVBox)
1200         self.cellFrame.setContentsMargins(2, 2, 2, 2)
1201 
1202         self.itemGrid.addWidget(self.cellFrame, 1, 1, 1, 1, QtCore.Qt.AlignCenter)
1203 
1204         self.componentName = {}
1205         self.componentImg = {}
1206         self.componentQty = {}
1207 
1208         for j in range(0, 3):
1209             self.componentName[j] = QLabelA('', 'White-None', None, 11)
1210             self.componentImg[j] = QLabelImgA('', self.blankImg, 'White-None')
1211             self.componentQty[j] = QLabelA('', 'White-None', None, 11)
1212             self.componentName[j].setWordWrap(True)
1213 
1214             self.compcellFrame = QtWidgets.QFrame()
1215             self.compcellFrame.setStyleSheet('background-color: white;\ 
1216                                               border-radius: 6')
1217             self.compcellFrame.setFixedSize(100, 120)
1218             cellVBox = QtWidgets.QVBoxLayout()
1219             cellVBox.setContentsMargins(0, 0, 0, 0)
1220             cellVBox.addWidget(self.componentName[j])
1221             cellVBox.addWidget(self.componentImg[j])
1222             cellVBox.addWidget(self.componentQty[j])
1223             self.compcellFrame.setLayout(cellVBox)
1224             self.compcellFrame.setContentsMargins(2, 2, 2, 2)
1225 
1226             self.itemGrid.addWidget(self.compcellFrame, 1, j + 2, 1, 1, QtCore.Qt.AlignCenter)
1227 
1228         self.itemFrame.setLayout(self.itemGrid)
1229 
1230         self.changeBlueprintB = QPushButtonA('Change', 'White-Square', 150)
1231         self.changeBlueprintB.clicked.connect(lambda: self.main.blueprintSelectFrame.displayInfo(self.selectedTool))
1232 
1233         self.blueprintSubGrid.addWidget(self.curBlueprintL, 1, 1, 1, 3)
1234         self.blueprintSubGrid.addWidget(self.itemFrame, 2, 1, 1, 3, QtCore.Qt.AlignCenter)
1235         self.blueprintSubGrid.addWidget(self.changeBlueprintB, 3, 2, 1, 1)
1236         self.blueprintWidget.setLayout(self.blueprintSubGrid)
1237         self.vbox.addWidget(self.blueprintWidget)
1238 
1239         # Inventory Layout
1240         self.inventoryWidget = QtWidgets.QFrame()
1241         self.inventoryWidget.setObjectName('inventoryWidget')
1242         self.inventoryWidget.setStyleSheet('#inventoryWidget{background-color: rgb(243, 243, 243);\ 
1243                                            margin:20px;\ 
1244                                            border:1px solid black;}')
1245         self.invSubGrid = QtWidgets.QGridLayout()
1246         self.invSubGrid.setSpacing(14)
1247         self.invSubGrid.setContentsMargins(14, 14, 14, 14)
1248         self.invTitleL = QLabelA('Inventory', 'Light-Gray-None', None, 13)
1249 
1250         self.inventoryNameL = {}
1251         self.inventoryImgL = {}
1252         self.inventoryQtyL = {}
1253 
1254         for i in range(0, 6):
1255             self.inventoryNameL[i] = QLabelA('', 'Light-Gray-None', 150)
1256             self.inventoryImgL[i] = QLabelImgA('', self.blankImg, 'Light-Gray-None', 150)
1257             self.inventoryQtyL[i] = QLabelA('-', 'Light-Gray-None', 150)
1258 
1259             self.inventoryNameL[i].setFixedHeight(20)
1260             self.inventoryImgL[i].setFixedHeight(20)
1261             self.inventoryQtyL[i].setFixedHeight(20)
1262 
1263             self.invSubGrid.addWidget(self.inventoryImgL[i], i + 2, 1)
1264             self.invSubGrid.addWidget(self.inventoryQtyL[i], i + 2, 2)
1265             self.invSubGrid.addWidget(self.inventoryNameL[i], i + 2, 3)
1266 
1267         self.clearInvB = QPushButtonA('Clear Inventory', 'White-Square', 150)
1268         self.clearInvB.clicked.connect(lambda: self.clearInventory())
1269 
1270         self.invSubGrid.addWidget(self.invTitleL, 1, 1, 1, 3)
1271         self.invSubGrid.addWidget(self.clearInvB, 9, 2)
1272 
1273         self.inventoryWidget.setLayout(self.invSubGrid)
1274         self.vbox.addWidget(self.inventoryWidget)
1275 
1276         # Qty Layout
1277         self.qtyWidget = QtWidgets.QFrame()
1278         self.qtyWidget.setObjectName('qtyWidget')
1279         self.qtyWidget.setStyleSheet('#qtyWidget{background-color: rgb(243, 243, 243);\ 
1280                                            margin:20px;\ 
1281                                            border:1px solid black;}')
1282         self.qtySubGrid = QtWidgets.QGridLayout()
1283         self.qtySubGrid.setContentsMargins(14, 14, 14, 14)
1284         self.changeQtyCurrentL = QLabelA('None', 'Light-Gray-None', None, 12)
1285         self.changeQtyMaxL = QLabelA('None', 'Light-Gray-None', None, 11)
1286         self.changeQty1B = QPushButtonA('1', 'White-Square', 150)
1287         self.changeQty2B = QPushButtonA('2', 'White-Square', 150)
1288         self.changeQty3B = QPushButtonA('3', 'White-Square', 150)
1289 
1290         self.changeQty1B.clicked.connect(lambda: self.setStarterQuantity(1))
1291         self.changeQty2B.clicked.connect(lambda: self.setStarterQuantity(2))
1292         self.changeQty3B.clicked.connect(lambda: self.setStarterQuantity(3))
1293 
1294         self.qtySubGrid.addWidget(self.changeQtyCurrentL, 1, 1, 1, 3)
1295         self.qtySubGrid.addWidget(self.changeQtyMaxL, 2, 1, 1, 3)
1296         self.qtySubGrid.addWidget(self.changeQty1B, 3, 1, 1, 1)
1297         self.qtySubGrid.addWidget(self.changeQty2B, 3, 2, 1, 1)
1298         self.qtySubGrid.addWidget(self.changeQty3B, 3, 3, 1, 1)
1299         self.qtyWidget.setLayout(self.qtySubGrid)
1300         self.vbox.addWidget(self.qtyWidget)
1301 
1302         # Splitter Layout
1303         self.splitWidget = QtWidgets.QFrame()
1304         self.splitWidget.setObjectName('splitWidget')
1305         self.splitWidget.setStyleSheet('#splitWidget{background-color: rgb(243, 243, 243);\ 
1306                                            margin:20px;\ 
1307                                            border:1px solid black;}')
1308         self.splitSubGrid = QtWidgets.QGridLayout()
1309         self.splitSubGrid.setContentsMargins(14, 14, 14, 14)
1310 
1311         self.splitLeftL = QLabelA(LEFT, 'Light-Gray-Square')
1312         self.incLeftB = QPushButtonA('+', 'Light-Gray-Square', 150)
1313         self.leftL = QLabelA('None', 'Light-Gray-Square')
1314         self.decLeftB = QPushButtonA('-', 'Light-Gray-Square', 150)
1315 
1316         self.splitStraightL = QLabelA('Straight', 'Light-Gray-Square')
1317         self.incStraightB = QPushButtonA('+', 'Light-Gray-Square', 150)
1318         self.straightL = QLabelA('None', 'Light-Gray-Square')
1319         self.decStraightB = QPushButtonA('-', 'Light-Gray-Square', 150)
1320 
1321         self.splitRightL = QLabelA(RIGHT, 'Light-Gray-Square')
1322         self.incRightB = QPushButtonA('+', 'Light-Gray-Square', 150)
1323         self.rightL = QLabelA('None', 'Light-Gray-Square')
1324         self.decRightB = QPushButtonA('-', 'Light-Gray-Square', 150)
1325 
1326         self.incLeftB.clicked.connect(lambda: self.setSplitSetting(0, 1))
1327         self.decLeftB.clicked.connect(lambda: self.setSplitSetting(0, -1))
1328         self.incStraightB.clicked.connect(lambda: self.setSplitSetting(1, 1))
1329         self.decStraightB.clicked.connect(lambda: self.setSplitSetting(1, -1))
1330         self.incRightB.clicked.connect(lambda: self.setSplitSetting(2, 1))
1331         self.decRightB.clicked.connect(lambda: self.setSplitSetting(2, -1))
1332 
1333         self.splitSubGrid.addWidget(self.splitLeftL, 2, 1, 1, 1)
1334         self.splitSubGrid.addWidget(self.incLeftB, 3, 1, 1, 1)
1335         self.splitSubGrid.addWidget(self.leftL, 4, 1, 1, 1)
1336         self.splitSubGrid.addWidget(self.decLeftB, 5, 1, 1, 1)
1337 
1338         self.splitSubGrid.addWidget(self.splitStraightL, 2, 2, 1, 1)
1339         self.splitSubGrid.addWidget(self.incStraightB, 3, 2, 1, 1)
1340         self.splitSubGrid.addWidget(self.straightL, 4, 2, 1, 1)
1341         self.splitSubGrid.addWidget(self.decStraightB, 5, 2, 1, 1)
1342 
1343         self.splitSubGrid.addWidget(self.splitRightL, 2, 3, 1, 1)
1344         self.splitSubGrid.addWidget(self.incRightB, 3, 3, 1, 1)
1345         self.splitSubGrid.addWidget(self.rightL, 4, 3, 1, 1)
1346         self.splitSubGrid.addWidget(self.decRightB, 5, 3, 1, 1)
1347         self.splitWidget.setLayout(self.splitSubGrid)
1348         self.vbox.addWidget(self.splitWidget)
1349 
1350         # Filter Layout
1351         self.filterWidget = QtWidgets.QFrame()
1352         self.filterWidget.setObjectName('filterWidget')
1353         self.filterWidget.setStyleSheet('#filterWidget{background-color: rgb(243, 243, 243);\ 
1354                                            margin:20px;\ 
1355                                            border:1px solid black;}')
1356         self.filterSubGrid = QtWidgets.QGridLayout()
1357         self.filterSubGrid.setContentsMargins(14, 14, 14, 14)
1358 
1359         self.filterLeftTitle = QLabelA('Left Filter:', 'Light-Gray-Square')
1360         self.filterLeftImage = QLabelImgA('', QtGui.QPixmap(''), 'Light-Gray-Square')
1361         self.filterLeftL = QLabelA('None', 'Light-Gray-Square')
1362         self.selFilterLeftB = QPushButtonA('Select', 'Light-Gray-Square', 150)
1363 
1364         self.filterRightTitle = QLabelA('Right Filter:', 'Light-Gray-Square')
1365         self.filterRightImage = QLabelImgA('', QtGui.QPixmap(''), 'Light-Gray-Square')
1366         self.filterRightL = QLabelA('None', 'Light-Gray-Square')
1367         self.selFilterRightB = QPushButtonA('Select', 'Light-Gray-Square', 150)
1368 
1369         self.filterSubGrid.addWidget(self.filterLeftTitle, 1, 1, 1, 1)
1370         self.filterSubGrid.addWidget(self.filterLeftImage, 2, 1, 1, 1)
1371         self.filterSubGrid.addWidget(self.filterLeftL, 3, 1, 1, 1)
1372         self.filterSubGrid.addWidget(self.selFilterLeftB, 4, 1, 1, 1)
1373 
1374         self.filterSubGrid.addWidget(self.filterRightTitle, 1, 3, 1, 1)
1375         self.filterSubGrid.addWidget(self.filterRightImage, 2, 3, 1, 1)
1376         self.filterSubGrid.addWidget(self.filterRightL, 3, 3, 1, 1)
1377         self.filterSubGrid.addWidget(self.selFilterRightB, 4, 3, 1, 1)
1378         self.filterWidget.setLayout(self.filterSubGrid)
1379         self.vbox.addWidget(self.filterWidget)
1380 
1381         # Teleporter Layout
1382         self.teleportWidget = QtWidgets.QFrame()
1383         self.teleportWidget.setObjectName('teleportWidget')
1384         self.teleportWidget.setStyleSheet('#teleportWidget{background-color: rgb(243, 243, 243);\ 
1385                                            margin:20px;\ 
1386                                            border:1px solid black;}')
1387         self.teleportSubGrid = QtWidgets.QGridLayout()
1388         self.teleportSubGrid.setContentsMargins(14, 14, 14, 14)
1389 
1390         self.incB = QPushButtonA('+', 'Light-Gray-Square', 200)
1391         self.IDNumL = QLabelA('None', 'Light-Gray-Square', 200)
1392         self.decB = QPushButtonA('-', 'Light-Gray-Square', 200)
1393         self.TPStatus = QLabelA('None', 'Light-Gray-Square', 200)
1394 
1395         self.incB.clicked.connect(lambda: self.setTeleporterID(1))
1396         self.decB.clicked.connect(lambda: self.setTeleporterID(-1))
1397 
1398         self.teleportSubGrid.addWidget(self.incB, 1, 2, 1, 1)
1399         self.teleportSubGrid.addWidget(self.IDNumL, 2, 2, 1, 1)
1400         self.teleportSubGrid.addWidget(self.decB, 3, 2, 1, 1)
1401         self.teleportSubGrid.addWidget(self.TPStatus, 4, 2, 1, 1, QtCore.Qt.AlignCenter)
1402         self.teleportWidget.setLayout(self.teleportSubGrid)
1403         self.vbox.addWidget(self.teleportWidget)
1404 
1405         # General
1406         self.contents.setLayout(self.vbox)
1407 
1408         self.blueprintWidget.hide()
1409         self.inventoryWidget.hide()
1410         self.qtyWidget.hide()
1411         self.splitWidget.hide()
1412         self.filterWidget.hide()
1413         self.teleportWidget.hide()
1414 
1415     def displayInfo(self, tool):
1416         self.main.closeMode()
1417         self.main.closeMenus()
1418         self.main.raiseFrame(self)
1419 
1420         self.selectedTool = tool
1421         print(self.main.toolPropertiesFrame.selectedTool)
1422 
1423         # Hide all sub-menus to start then show select few
1424         self.blueprintWidget.hide()
1425         self.inventoryWidget.hide()
1426         self.qtyWidget.hide()
1427         self.splitWidget.hide()
1428         self.filterWidget.hide()
1429         self.teleportWidget.hide()
1430 
1431         self.titleL.setText(tool.type)
1432 
1433         # Blueprint DisplayInfo
1434         if tool.type in [STARTER, CRAFTER]:
1435             self.blueprintWidget.show()
1436 
1437             # Clears anything there first
1438             self.baseName.setText('')
1439             self.baseImg.setPixmap(self.blankImg)
1440             self.baseQty.setText('')
1441             for j in range(0, 3):
1442                 self.componentName[j].setText('')
1443                 self.componentImg[j].setPixmap(self.blankImg)
1444                 self.componentQty[j].setText('')
1445 
1446             if tool.selectedBlueprint is not None:
1447                 self.baseName.setText('%s' % tool.selectedBlueprint)
1448                 self.baseImg.setPixmap(self.main.materialLib.lib[tool.selectedBlueprint]['image'].scaled(24, 24))
1449                 self.baseQty.setText('1')
1450 
1451                 for j, component in enumerate(self.main.materialLib.lib[tool.selectedBlueprint]['components']):
1452                     self.componentName[j].setText('%s' % component)
1453                     self.componentImg[j].setPixmap(self.main.materialLib.lib[component]['image'].scaled(24, 24))
1454                     self.componentQty[j].setText(
1455                         '%s' % self.main.materialLib.lib[tool.selectedBlueprint]['components'][component])
1456 
1457         # Inventory DisplayInfo
1458         if tool.type in [CRAFTER, DRAWER, CUTTER, FURNACE, PRESS]:
1459             self.inventoryWidget.show()
1460 
1461             # Clear Everything First
1462             for i in range(0, len(self.inventoryImgL)):
1463                 self.inventoryImgL[i].setPixmap(self.blankImg)
1464                 self.inventoryQtyL[i].setText('-')
1465                 self.inventoryNameL[i].setText('')
1466 
1467             for i, (key, value) in enumerate(tool.contains.items()):
1468                 self.inventoryImgL[i].setPixmap(self.main.materialLib.lib[key]['image'].scaled(16, 16))
1469                 self.inventoryQtyL[i].setText(str(value))
1470                 self.inventoryNameL[i].setText(key)
1471 
1472         # Qty DisplayInfo
1473         if tool.type in [STARTER]:
1474             self.qtyWidget.show()
1475             self.changeQtyMaxL.setText('Max of %s Researched' % self.main.starterMaxSpawnQuantity)
1476             self.changeQtyCurrentL.setText('Materials Generated Per Operation')
1477             self.setStarterQuantity(tool.starterQuantity)  # Sets button marked and enabled status
1478 
1479         # Splitter DisplayInfo
1480         if tool.type in [SPLITTER_LEFT, SPLITTER_RIGHT, SPLITTER_TEE, SPLITTER_3WAY]:
1481             self.splitWidget.show()
1482 
1483             self.leftL.setText(str(tool.splitSetting[0]))
1484             self.straightL.setText(str(tool.splitSetting[1]))
1485             self.rightL.setText(str(tool.splitSetting[2]))
1486 
1487             if tool.type in [SPLITTER_3WAY, SPLITTER_LEFT, SPLITTER_TEE]:
1488                 self.incLeftB.setStyleCode('White-Square')
1489                 self.incLeftB.setEnabled(True)
1490                 self.decLeftB.setStyleCode('White-Square')
1491                 self.decLeftB.setEnabled(True)
1492             else:
1493                 self.incLeftB.setStyleCode('Gray-Square')
1494                 self.incLeftB.setEnabled(False)
1495                 self.leftL.setText('-')
1496                 self.decLeftB.setStyleCode('Gray-Square')
1497                 self.decLeftB.setEnabled(False)
1498 
1499             if tool.type in [SPLITTER_3WAY, SPLITTER_LEFT, SPLITTER_RIGHT]:
1500                 self.incStraightB.setStyleCode('White-Square')
1501                 self.incStraightB.setEnabled(True)
1502                 self.decStraightB.setStyleCode('White-Square')
1503                 self.decStraightB.setEnabled(True)
1504             else:
1505                 self.incStraightB.setStyleCode('Gray-Square')
1506                 self.incStraightB.setEnabled(False)
1507                 self.straightL.setText('-')
1508                 self.decStraightB.setStyleCode('Gray-Square')
1509                 self.decStraightB.setEnabled(False)
1510 
1511             if tool.type in [SPLITTER_3WAY, SPLITTER_RIGHT, SPLITTER_TEE]:
1512                 self.incRightB.setStyleCode('White-Square')
1513                 self.incRightB.setEnabled(True)
1514                 self.decRightB.setStyleCode('White-Square')
1515                 self.decRightB.setEnabled(True)
1516             else:
1517                 self.incRightB.setStyleCode('Gray-Square')
1518                 self.incRightB.setEnabled(False)
1519                 self.rightL.setText('-')
1520                 self.decRightB.setStyleCode('Gray-Square')
1521                 self.decRightB.setEnabled(False)
1522 
1523         # Filter DisplayInfo
1524         if tool.type in [FILTER_LEFT, FILTER_RIGHT, FILTER_TEE]:
1525             self.filterWidget.show()
1526             if tool.filterLeft is not None:
1527                 self.filterLeftImage.setPixmap(
1528                     self.main.materialLib.lib[tool.filterLeft]['image'].scaled(40, 40))
1529             self.filterLeftL.setText(tool.filterLeft)
1530             if tool.filterRight is not None:
1531                 self.filterRightImage.setPixmap(
1532                     self.main.materialLib.lib[tool.filterRight]['image'].scaled(40, 40))
1533             self.filterRightL.setText(tool.filterRight)
1534 
1535             self.selFilterLeftB.clicked.connect(lambda: self.main.filterSelectFrame.displayInfo(LEFT))
1536             print(self.main.toolPropertiesFrame.selectedTool.type)
1537             self.selFilterRightB.clicked.connect(lambda: self.main.filterSelectFrame.displayInfo(RIGHT))
1538 
1539             if tool.type in [FILTER_LEFT, FILTER_TEE]:
1540                 self.selFilterLeftB.setStyleCode('White-Square')
1541                 self.selFilterLeftB.setEnabled(True)
1542             else:
1543                 self.selFilterLeftB.setStyleCode('Gray-Square')
1544                 self.selFilterLeftB.setEnabled(False)
1545 
1546             if tool.type in [FILTER_RIGHT, FILTER_TEE]:
1547                 self.selFilterRightB.setStyleCode('White-Square')
1548                 self.selFilterRightB.setEnabled(True)
1549             else:
1550                 self.selFilterRightB.setStyleCode('Gray-Square')
1551                 self.selFilterRightB.setEnabled(False)
1552 
1553         # Teleporter DisplayInfo
1554         if tool.type in [TELEPORTER_INPUT, TELEPORTER_OUTPUT]:
1555             self.teleportWidget.show()
1556             if tool.teleporterActivated:
1557                 self.TPStatus.setText('Status: Activated')
1558                 self.TPStatus.setStyleCode('Green-Round')
1559             else:
1560                 self.TPStatus.setText('Status: Deactivated')
1561                 self.TPStatus.setStyleCode('Red-Round')
1562             self.IDNumL.setText(str(tool.teleporterID))
1563 
1564     def clearInventory(self):
1565         self.selectedTool.clearInventory()
1566         self.displayInfo(self.selectedTool)
1567         self.main.raiseFrame(self)
1568 
1569     def refreshInventory(self):
1570         self.displayInfo(self.selectedTool)
1571         self.main.raiseFrame(self)
1572 
1573     def setStarterQuantity(self, quantity):
1574         self.selectedTool.starterQuantity = quantity
1575 
1576         # Enable Button Based On Research Status
1577         if self.main.starterMaxSpawnQuantity >= 1:
1578             self.main.toolPropertiesFrame.changeQty1B.setEnabled(True)
1579             self.main.toolPropertiesFrame.changeQty1B.setStyleCode('White-Square')
1580         else:
1581             self.main.toolPropertiesFrame.changeQty1B.setEnabled(False)
1582             self.main.toolPropertiesFrame.changeQty1B.setStyleCode('Gray-Square')
1583         if self.main.starterMaxSpawnQuantity >= 2:
1584             self.main.toolPropertiesFrame.changeQty2B.setEnabled(True)
1585             self.main.toolPropertiesFrame.changeQty2B.setStyleCode('White-Square')
1586         else:
1587             self.main.toolPropertiesFrame.changeQty2B.setEnabled(False)
1588             self.main.toolPropertiesFrame.changeQty2B.setStyleCode('Gray-Square')
1589         if self.main.starterMaxSpawnQuantity >= 3:
1590             self.main.toolPropertiesFrame.changeQty3B.setEnabled(True)
1591             self.main.toolPropertiesFrame.changeQty3B.setStyleCode('White-Square')
1592         else:
1593             self.main.toolPropertiesFrame.changeQty3B.setEnabled(False)
1594             self.main.toolPropertiesFrame.changeQty3B.setStyleCode('Gray-Square')
1595 
1596         # Visually Mark Selected Option
1597         if quantity == 1:
1598             self.main.toolPropertiesFrame.changeQty1B.setStyleCode('Blue-Square')
1599         elif quantity == 2:
1600             self.main.toolPropertiesFrame.changeQty2B.setStyleCode('Blue-Square')
1601         elif quantity == 3:
1602             self.main.toolPropertiesFrame.changeQty3B.setStyleCode('Blue-Square')
1603 
1604     def setSplitSetting(self, direction, amount):
1605         if self.selectedTool.splitSetting[direction] + amount >= 1:  # Prevent setting from going below 1
1606             self.selectedTool.splitSetting[direction] += amount
1607             if self.selectedTool.type in [SPLITTER_3WAY, SPLITTER_LEFT, SPLITTER_TEE]:
1608                 self.leftL.setText('%s' % self.selectedTool.splitSetting[0])
1609             if self.selectedTool.type in [SPLITTER_3WAY, SPLITTER_LEFT, SPLITTER_RIGHT]:
1610                 self.straightL.setText('%s' % self.selectedTool.splitSetting[1])
1611             if self.selectedTool.type in [SPLITTER_3WAY, SPLITTER_RIGHT, SPLITTER_TEE]:
1612                 self.rightL.setText('%s' % self.selectedTool.splitSetting[2])
1613         else:
1614             self.main.updateMessage('Setting must be greater than zero')
1615 
1616     def setTeleporterID(self, adjustment):
1617         if self.selectedTool.teleporterID is None:
1618             self.selectedTool.teleporterID = 1
1619         elif self.selectedTool.teleporterID + adjustment >= 1:
1620             self.selectedTool.teleporterID += adjustment
1621         else:
1622             self.main.updateMessage('Setting must be greater than zero')
1623 
1624         self.main.activateValidTeleporters()
1625 
1626         self.displayInfo(self.selectedTool)
1627         self.main.raiseFrame(self)
1628 
1629 
1630 class blueprintSelectMenu(scrollingBaseMenuFrame):
1631     def __init__(self, parent=None, main=None):
1632         super(blueprintSelectMenu, self).__init__(parent, main)
1633 
1634         # Set Menu Parameters
1635         self.titleL.setText('Select Blueprint')
1636 
1637         # Set Grid Contents
1638         self.grid = QtWidgets.QGridLayout()
1639         self.grid.setContentsMargins(100, 20, 100, 20)
1640 
1641         # Generate Blueprints List
1642         self.wids = {}
1643         self.cellFrame = {}
1644         for i, material in enumerate(self.main.materialLib.lib):
1645             self.wids[material] = {}
1646             self.wids[material]['base'] = {}
1647             self.wids[material]['base']['img'] = QLabelImgA(
1648                 '', self.main.materialLib.lib[material]['image'].scaled(32, 32), 'White-None')
1649             self.wids[material]['base']['label'] = QPushButtonA(material, 'White-Square', 200)
1650 
1651             self.cellFrame[material] = QtWidgets.QFrame()
1652             self.cellFrame[material].setStyleSheet('background-color: white;\ 
1653                                                     border-radius: 6;')
1654             cellVBox = QtWidgets.QHBoxLayout(self.cellFrame[material])
1655             cellVBox.addWidget(self.wids[material]['base']['img'])
1656             cellVBox.addWidget(self.wids[material]['base']['label'])
1657 
1658             self.grid.addWidget(self.cellFrame[material])
1659 
1660         self.contents.setLayout(self.grid)
1661 
1662     def displayInfo(self, tool):
1663         self.main.closeMode()
1664         self.main.closeMenus()
1665         self.main.raiseFrame(self)
1666 
1667         self.selectedTool = tool
1668 
1669         for i, material in enumerate(self.main.materialLib.lib):
1670             if self.main.materialLib.lib[material]['class'] \
1671                     == self.main.machineLib.lib[self.selectedTool.type]['blueprintType'] \
1672                     and material in self.main.unlockedBlueprints:
1673                 self.wids[material]['base']['label'].show()
1674                 self.wids[material]['base']['img'].show()
1675                 self.cellFrame[material].show()
1676                 self.wids[material]['base']['label'].clicked.connect(
1677                     lambda state, material=material: self.setSelectedBlueprint(material))
1678             else:
1679                 self.wids[material]['base']['label'].hide()
1680                 self.wids[material]['base']['img'].hide()
1681                 self.cellFrame[material].hide()
1682 
1683             if material == self.selectedTool.selectedBlueprint:
1684                 self.wids[material]['base']['label'].setStyleCode('Blue-Square')
1685             else:
1686                 self.wids[material]['base']['label'].setStyleCode('White-Square')
1687 
1688     def setSelectedBlueprint(self, material):
1689         self.main.closeMenus()
1690         self.selectedTool.setSelectedBlueprint(material)
1691 
1692 
1693 class filterSelectMenu(scrollingBaseMenuFrame):
1694     def __init__(self, parent=None, main=None):
1695         super(filterSelectMenu, self).__init__(parent, main)
1696 
1697         # Set Menu Parameters
1698         self.titleL.setText('Select Filter')
1699 
1700         # Set Grid Contents
1701         self.grid = QtWidgets.QGridLayout()
1702         self.grid.setContentsMargins(100, 20, 100, 20)
1703 
1704         # Generate Blueprints List
1705         self.wids = {}
1706         self.cellFrame = {}
1707         for i, material in enumerate(self.main.materialLib.lib):
1708             self.wids[material] = {}
1709             self.wids[material]['base'] = {}
1710             self.wids[material]['base']['label'] = QPushButtonA(material, 'White-Square', 200)
1711             self.wids[material]['base']['img'] = QLabelImgA(
1712                 '', self.main.materialLib.lib[material]['image'].scaled(40, 40), 'White-None')
1713 
1714             self.cellFrame[material] = QtWidgets.QFrame()
1715             self.cellFrame[material].setStyleSheet('background-color: white; border-radius: 6;')
1716             cellVBox = QtWidgets.QHBoxLayout()
1717             cellVBox.addWidget(self.wids[material]['base']['img'])
1718             cellVBox.addWidget(self.wids[material]['base']['label'])
1719             self.cellFrame[material].setLayout(cellVBox)
1720             self.grid.addWidget(self.cellFrame[material])
1721 
1722         self.contents.setLayout(self.grid)
1723 
1724     def displayInfo(self, side):
1725         self.main.closeMode()
1726         self.main.closeMenus()
1727         self.main.raiseFrame(self)
1728 
1729         for i, material in enumerate(self.main.materialLib.lib):
1730             self.wids[material]['base']['label'].clicked.connect(
1731                 lambda state, material=material, side=side: self.setFilter(material, side))
1732 
1733             print(self.main.toolPropertiesFrame.selectedTool.type)
1734 
1735 
1736             if side == LEFT and material == self.main.toolPropertiesFrame.selectedTool.filterLeft:
1737                 self.cellFrame[material].setStyleSheet('background-color: steelblue; border-radius: 6;')
1738             if side == RIGHT and material == self.main.toolPropertiesFrame.selectedTool.filterRight:
1739                 self.cellFrame[material].setStyleSheet('background-color: steelblue; border-radius: 6;')
1740 
1741     def setFilter(self, material, side):
1742         self.main.closeMenus()
1743         if side == LEFT:
1744             self.main.toolPropertiesFrame.selectedTool.filterLeft = material
1745         elif side == RIGHT:
1746             self.main.toolPropertiesFrame.selectedTool.filterRight = material
1747 
1748 
1749 class buildMenu(baseMenuFrame):
1750     def __init__(self, parent=None, main=None):
1751         super(buildMenu, self).__init__(parent, main)
1752 
1753         # Set Menu Parameters
1754         self.setMaximumHeight(600)
1755         self.titleL.setText('Build')
1756 
1757         # Set Grid Contents
1758         self.grid = QtWidgets.QGridLayout()
1759         self.grid.setSpacing(0)
1760         self.grid.setVerticalSpacing(6)
1761         self.grid.setContentsMargins(10, 10, 10, 10)
1762         self.grid.setColumnStretch(0, 1)
1763         self.grid.setColumnStretch(3, 1)
1764         self.grid.setColumnStretch(6, 1)
1765         self.grid.setColumnStretch(9, 1)
1766         self.grid.setColumnStretch(12, 1)
1767 
1768         self.setMinimumWidth(1400)
1769 
1770         self.wids = {}
1771         for i, machine in enumerate(self.main.machineLib.lib):
1772             self.itemFrame = QtWidgets.QFrame()
1773             self.itemFrame.setStyleSheet('QFrame{background-color: gray; border: 1px solid black}')
1774             self.itemGrid = QtWidgets.QGridLayout()
1775             self.wids[machine] = {}
1776             self.wids[machine]['img'] = QLabelImgA('', self.main.machineLib.lib[machine]['imageComposite'],
1777                                                    'White-Square')
1778             self.wids[machine]['label'] = QPushButtonA(
1779                 '%s - $%s' % (machine, self.main.shortNum(self.main.machineLib.lib[machine]['buildCost'])),
1780                 'White-Round', 230)
1781             self.wids[machine]['label'].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
1782             self.wids[machine]['label'].clicked.connect(lambda state, machine=machine: self.main.buildMode(machine))
1783             self.itemGrid.addWidget(self.wids[machine]['img'], 0, 0)
1784             self.itemGrid.addWidget(self.wids[machine]['label'], 0, 1)
1785 
1786             # Create Locked Machines Widgets
1787             self.wids[machine]['lockImg'] = QLabelImgA('', self.main.imageLib.lib['Key Lock']['image'], 'White-Square')
1788             self.wids[machine]['lockLabel'] = QPushButtonA(
1789                 'Unlock\n%s - $%s' % (machine, self.main.shortNum(self.main.machineLib.lib[machine]['unlock'])),
1790                 'Blue-Round', 230)
1791             self.wids[machine]['lockLabel'].setSizePolicy(QtWidgets.QSizePolicy.Expanding,
1792                                                           QtWidgets.QSizePolicy.Expanding)
1793             self.wids[machine]['lockLabel'].clicked.connect(
1794                 lambda state, machine=machine: self.main.unlockMachine(machine))
1795             self.itemGrid.addWidget(self.wids[machine]['lockImg'], 0, 0)
1796             self.itemGrid.addWidget(self.wids[machine]['lockLabel'], 0, 1)
1797 
1798             self.itemFrame.setLayout(self.itemGrid)
1799             self.grid.addWidget(self.itemFrame, (i * 2) % 10, i // 5 * 3 + 1)
1800 
1801         self.contents.setLayout(self.grid)
1802 
1803         self.reset()
1804 
1805     def reset(self):
1806         for i, machine in enumerate(self.main.machineLib.lib):
1807             if machine in self.main.unlockedMachines:
1808                 self.wids[machine]['lockImg'].lower()
1809                 self.wids[machine]['lockLabel'].lower()
1810             else:
1811                 self.wids[machine]['lockImg'].raise_()
1812                 self.wids[machine]['lockLabel'].raise_()
1813 
1814 
1815 class blueprintsMenu(scrollingBaseMenuFrame):
1816     # Setup:
1817     # contents [grid]
1818     #    itemframe [itemGrid]
1819     #        cellFrame [cellGrid]
1820     def __init__(self, parent=None, main=None):
1821         super(blueprintsMenu, self).__init__(parent, main)
1822 
1823         # Set Menu Parameters
1824         self.setMinimumWidth(1000)
1825         self.titleL.setText('Blueprints')
1826 
1827         # Set Grid Contents
1828         self.grid = QtWidgets.QGridLayout()
1829         self.grid.setSpacing(2)
1830         self.grid.setContentsMargins(2, 2, 2, 2)
1831 
1832         # Generate Blueprints List
1833         self.wids = {}
1834         for i, material in enumerate(self.main.materialLib.lib):
1835             if self.main.materialLib.lib[material]['class'] == 'Tier2':  # Exclude Basic & Tier 1 materials
1836                 self.itemFrame = QtWidgets.QFrame()
1837                 self.itemFrame.setContentsMargins(10, 2, 10, 2)
1838 
1839                 self.itemGrid = QtWidgets.QGridLayout()
1840                 self.itemGrid.setContentsMargins(10, 2, 10, 2)
1841                 self.itemGrid.setAlignment(QtCore.Qt.AlignLeft)
1842 
1843                 self.wids[material] = {}
1844                 self.wids[material]['base'] = {}
1845                 self.wids[material]['base']['label'] = QLabelA('%s' % material, 'Blue-None', None, 11)
1846                 self.wids[material]['base']['img'] = QLabelImgA(
1847                     '', self.main.materialLib.lib[material]['image'].scaled(32, 32), 'Blue-None')
1848                 self.wids[material]['base']['price'] = QLabelA(
1849                     '$%s' % self.main.shortNum(self.main.materialLib.lib[material]['value']), 'Blue-None', None, 11)
1850 
1851                 cellFrame = QtWidgets.QFrame()
1852                 cellFrame.setStyleSheet('background-color: steelblue;\ 
1853                                          border: 1px solid black;\ 
1854                                          border-radius: 4')
1855                 cellFrame.setFixedSize(180, 100)
1856                 cellGrid = QtWidgets.QGridLayout()
1857                 cellGrid.setContentsMargins(2, 0, 2, 2)
1858                 cellGrid.addWidget(self.wids[material]['base']['label'], 0, 0, 1, 2)
1859                 cellGrid.addWidget(self.wids[material]['base']['img'], 1, 1, 1, 1)
1860                 cellGrid.addWidget(self.wids[material]['base']['price'], 1, 0, 1, 1)
1861                 cellFrame.setLayout(cellGrid)
1862 
1863                 cellFrame.setContentsMargins(10, 2, 10, 10)
1864 
1865                 self.itemGrid.addWidget(cellFrame, 1, 1, 1, 1, QtCore.Qt.AlignCenter)
1866                 self.itemGrid.setColumnMinimumWidth(1, 200)  # Only changes left frame column width
1867                 self.itemGrid.setRowMinimumHeight(1, 120)
1868 
1869                 # Add Lock Widgets Over Components
1870                 self.wids[material]['base']['lock'] = QPushButtonA(
1871                     'Unlock Blueprint\n$%s' % self.main.shortNum(self.main.materialLib.lib[material]['unlock']),
1872                     'MediumGray-Round')
1873                 self.wids[material]['base']['lock'].setFixedSize(550, 100)
1874                 self.itemGrid.addWidget(self.wids[material]['base']['lock'], 1, 2, 1, 3, QtCore.Qt.AlignCenter)
1875                 self.wids[material]['base']['lock'].clicked.connect(
1876                     lambda state, material=material: self.main.unlockBlueprint(material))
1877 
1878                 # Create cover to place in front of lock button when item is unlocked
1879                 self.wids[material]['base']['cover'] = QLabelA('', 'LightGray-Round')
1880                 self.itemGrid.addWidget(self.wids[material]['base']['cover'], 1, 1, 1, 4)
1881 
1882                 # Add components for each item
1883                 for j, component in enumerate(self.main.materialLib.lib[material]['components']):
1884                     self.wids[material][j] = {}
1885                     self.wids[material][j]['label'] = QLabelA('%s' % component, 'White-None', None, 11)
1886                     self.wids[material][j]['img'] = QLabelImgA(
1887                         '', self.main.materialLib.lib[component]['image'].scaled(24, 24), 'White-None')
1888                     self.wids[material][j]['qty'] = QLabelA(
1889                         '%s' % self.main.materialLib.lib[material]['components'][component], 'White-None')
1890 
1891                     self.wids[material][j]['qty'].setMargin(10)  # Margin around the qty label
1892 
1893                     cellFrame = QtWidgets.QFrame()
1894                     cellFrame.setStyleSheet('background-color: white;\ 
1895                                              border: 1px solid black;\ 
1896                                              border-radius: 6')
1897                     cellFrame.setFixedSize(180, 100)
1898 
1899                     cellGrid = QtWidgets.QGridLayout(cellFrame)
1900                     cellGrid.setContentsMargins(10, 2, 10, 10)
1901                     cellGrid.addWidget(self.wids[material][j]['label'], 0, 0, 1, 2)
1902                     cellGrid.addWidget(self.wids[material][j]['img'], 1, 1, 1, 1, QtCore.Qt.AlignLeft)
1903                     cellGrid.addWidget(self.wids[material][j]['qty'], 1, 0, 1, 1, QtCore.Qt.AlignRight)
1904 
1905                     cellFrame.setContentsMargins(0, 0, 0, 0)
1906 
1907                     self.itemGrid.addWidget(cellFrame, 1, j + 2, 1, 1, QtCore.Qt.AlignCenter)
1908 
1909                 # self.itemFrame.setMinimumWidth(1000)
1910                 self.itemFrame.setLayout(self.itemGrid)
1911                 self.grid.addWidget(self.itemFrame, i, 1)
1912 
1913         self.grid.setColumnStretch(0, 1)
1914         self.grid.setColumnStretch(5, 1)
1915 
1916         self.contents.setLayout(self.grid)
1917 
1918         self.reset()
1919 
1920     def reset(self):
1921         # Mark Locked Widgets Only
1922         for i, material in enumerate(self.main.materialLib.lib):
1923             if self.main.materialLib.lib[material]['class'] == 'Tier2':  # Exclude Basic and Tier1 from listing
1924                 if material in self.main.unlockedBlueprints:
1925                     self.wids[material]['base']['cover'].lower()
1926                     self.wids[material]['base']['lock'].lower()
1927                 else:
1928                     self.wids[material]['base']['cover'].lower()
1929                     self.wids[material]['base']['lock'].raise_()
1930 
1931 
1932 class researchMenu(scrollingBaseMenuFrame):
1933     def __init__(self, parent=None, main=None):
1934         super(researchMenu, self).__init__(parent, main)
1935 
1936         self.setMinimumWidth(1000)
1937 
1938         # Set Menu Parameters
1939         self.titleL.setText('Research')
1940 
1941         # Set Grid Contents
1942         self.grid = QtWidgets.QGridLayout()
1943         self.grid.setSpacing(10)
1944         self.grid.setContentsMargins(40, 40, 40, 40)
1945 
1946         # List of research options with information
1947         self.wids = {}
1948         for i, option in enumerate(self.main.researchLib.lib):
1949             self.wids[option] = {}
1950             self.wids[option]['desc'] = QLabelA(
1951                 '%s' % str(self.main.researchLib.lib[option]['description']), 'White-None', 400)
1952             self.wids[option]['desc'].setWordWrap(True)
1953             self.wids[option]['lock'] = QPushButtonA(
1954                 'Unlock for $%s' % self.main.shortNum(self.main.researchLib.lib[option]['cost']), 'MediumGray-Round',
1955                 200)
1956             self.wids[option]['lock'].clicked.connect(lambda state, option=option: self.main.unlockResearch(option))
1957 
1958             cellFrame = QtWidgets.QFrame()
1959             cellFrame.setStyleSheet('QFrame{background-color: white;\ 
1960                                      border: 1px solid black;\ 
1961                                      border-radius: 4}')
1962             cellGrid = QtWidgets.QGridLayout()
1963             cellGrid.setSpacing(6)
1964             cellGrid.addWidget(self.wids[option]['desc'], 0, 0, 1, 1, QtCore.Qt.AlignCenter)
1965             cellGrid.addWidget(self.wids[option]['lock'], 2, 0, 1, 1, QtCore.Qt.AlignCenter)
1966             cellFrame.setLayout(cellGrid)
1967 
1968             self.grid.setRowMinimumHeight(i * 2, 120)
1969             self.grid.addWidget(cellFrame, i // 2, i % 2 + 1)
1970 
1971         self.grid.setColumnStretch(0, 1)
1972         self.grid.setColumnStretch(3, 1)
1973 
1974         self.contents.setLayout(self.grid)
1975 
1976         self.reset()
1977 
1978     def reset(self):
1979         for i, option in enumerate(self.main.researchLib.lib):
1980             if option in self.main.unlockedResearch:
1981                 self.wids[option]['lock'].setText('Unlocked')
1982                 self.wids[option]['lock'].setDisabled(1)
1983                 self.wids[option]['lock'].setStyleCode('Green-Round')
1984             else:
1985                 self.wids[option]['lock'].setText(
1986                     'Unlock for $%s' % self.main.shortNum(self.main.researchLib.lib[option]['cost']))
1987                 self.wids[option]['lock'].setDisabled(0)
1988                 self.wids[option]['lock'].setStyleCode('MediumGray-Round')
1989 
1990 
1991 class assyLineMenu(baseMenuFrame):
1992     def __init__(self, parent=None, main=None):
1993         super(assyLineMenu, self).__init__(parent, main)
1994 
1995         # Set Menu Parameters
1996         self.setMaximumHeight(600)
1997         self.titleL.setText('Assembly Lines')
1998 
1999         # Set Grid Contents
2000         self.grid = QtWidgets.QGridLayout()
2001         self.grid.setSpacing(10)
2002         self.grid.setContentsMargins(20, 20, 20, 20)
2003 
2004         self.line2BLocked = QPushButtonA('Unlock Assembly Line 2 - $10M', 'MediumGray-Round', 300)
2005         self.line2BLocked.clicked.connect(lambda state: self.main.buyAssyLine('Line2', 10000000))
2006         self.grid.addWidget(self.line2BLocked, 0, 0)
2007 
2008         self.line3BLocked = QPushButtonA('Unlock Assembly Line 3 - $500M', 'MediumGray-Round', 300)
2009         self.line3BLocked.clicked.connect(lambda state: self.main.buyAssyLine('Line3', 500000000))
2010         self.grid.addWidget(self.line3BLocked, 1, 0)
2011 
2012         self.contents.setLayout(self.grid)
2013 
2014         self.reset()
2015 
2016     def reset(self):
2017         if 'Line2' in self.main.unlockedAssyLines:
2018             self.line2BLocked.setText('Assembly Line 2 - Unlocked')
2019             self.line2BLocked.setDisabled(True)
2020             self.line2BLocked.setStyleCode('Green-Round')
2021         else:
2022             self.line2BLocked.setText('Unlock Assembly Line 2 - $5M')
2023             self.line2BLocked.setDisabled(False)
2024             self.line2BLocked.setStyleCode('MediumGray-Round')
2025 
2026         if 'Line3' in self.main.unlockedAssyLines:
2027             self.line3BLocked.setText('Assembly Line 2 - Unlocked')
2028             self.line3BLocked.setDisabled(True)
2029             self.line3BLocked.setStyleCode('Green-Round')
2030         elif 'Line2' not in self.main.unlockedAssyLines:
2031             self.line3BLocked.setText('Assembly Line 3 - Not Available')
2032             self.line3BLocked.setDisabled(True)
2033             self.line3BLocked.setStyleCode('MediumGray-Round')
2034         else:
2035             self.line3BLocked.setText('Unlock Assembly Line 3 - $500M')
2036             self.line3BLocked.setDisabled(False)
2037             self.line3BLocked.setStyleCode('MediumGray-Round')
2038 
2039 
2040 class helpMenu(scrollingBaseMenuFrame):
2041     def __init__(self, parent=None, main=None):
2042         super(helpMenu, self).__init__(parent, main)
2043 
2044         # Set Menu Parameters
2045         self.setMinimumWidth(1000)
2046         self.titleL.setText('Help')
2047 
2048         # Set Grid Contents
2049         self.grid = QtWidgets.QGridLayout()
2050         self.grid.setSpacing(0)
2051         self.grid.setContentsMargins(40, 40, 40, 40)
2052 
2053         # List of help list items
2054         self.wids = {}
2055         for i, machine in enumerate(self.main.machineLib.lib):
2056             self.wids[machine] = {}
2057             self.wids[machine]['name'] = QLabelA(machine, 'White-None', None, 14)
2058             self.wids[machine]['image'] = QLabelImgA(
2059                 '', self.main.machineLib.lib[machine]['imageComposite'], 'White-None')
2060             self.wids[machine]['prop1'] = QLabelA(
2061                 'Build Cost: $%s' % self.main.shortNum(self.main.machineLib.lib[machine]['buildCost']), 'White-None')
2062             self.wids[machine]['prop2'] = QLabelA(
2063                 'Electricity Cost: $%s' % self.main.shortNum(self.main.machineLib.lib[machine]['opCost']),
2064                 'White-None')
2065             self.wids[machine]['desc'] = QLabelA(
2066                 'Description: %s' % self.main.machineLib.lib[machine]['description'], 'White-None')
2067 
2068             self.wids[machine]['desc'].setWordWrap(True)
2069 
2070             cellFrame = QtWidgets.QFrame()
2071             cellFrame.setStyleSheet('background-color: white;\ 
2072                                      border: 1px solid black;\ 
2073                                      border-radius: 6;')
2074             cellGrid = QtWidgets.QGridLayout()
2075             cellGrid.setSpacing(10)
2076             cellGrid.addWidget(self.wids[machine]['name'], 0, 1, 1, 3)
2077             cellGrid.addWidget(self.wids[machine]['image'], 1, 1, 3, 1)
2078             cellGrid.addWidget(self.wids[machine]['prop1'], 2, 2, 1, 1)
2079             cellGrid.addWidget(self.wids[machine]['prop2'], 2, 3, 1, 1)
2080             cellGrid.addWidget(self.wids[machine]['desc'], 3, 2, 1, 2)
2081 
2082             cellGrid.setColumnMinimumWidth(1, 100)
2083             cellGrid.setColumnMinimumWidth(2, 200)
2084             cellGrid.setColumnMinimumWidth(3, 200)
2085             cellGrid.setColumnStretch(0, 1)
2086             cellGrid.setColumnStretch(4, 1)
2087 
2088             cellFrame.setLayout(cellGrid)
2089 
2090             self.grid.addWidget(cellFrame, i * 2, 1)
2091 
2092             self.grid.setRowMinimumHeight(i * 2, 80)
2093             self.grid.setRowMinimumHeight(i * 2 + 1, 6)
2094 
2095         self.grid.setColumnStretch(0, 1)
2096         self.grid.setColumnStretch(5, 1)
2097 
2098         self.contents.setLayout(self.grid)
2099 
2100 
2101 class incomeAnalysisMenu(baseMenuFrame):
2102     def __init__(self, parent=None, main=None):
2103         super(incomeAnalysisMenu, self).__init__(parent, main)
2104 
2105         # Set Menu Parameters
2106         self.setMaximumHeight(600)
2107         self.titleL.setText('Income\n(%s Second Intervals)' % INCOME_ANALYSIS_FREQ)
2108 
2109         # Set Grid Contents
2110         self.grid = QtWidgets.QGridLayout()
2111         self.grid.setSpacing(0)
2112         self.grid.setVerticalSpacing(0)
2113         self.grid.setContentsMargins(50, 50, 50, 50)
2114         self.grid.setColumnStretch(0, 1)
2115         self.grid.setColumnStretch(5, 1)
2116 
2117         self.wids = {}
2118 
2119         self.wids['MRHeader_Type'] = QLabelA('Resource', 'White-Square-Table-Title', 300)
2120         self.wids['MRHeader_Count'] = QLabelA('Count', 'White-Square-Table-Title', 150)
2121         self.wids['MRHeader_Profit'] = QLabelA('Profit', 'White-Square-Table-Title', 150)
2122         self.wids['MRHeader_PPS'] = QLabelA('Profit/s', 'White-Square-Table-Title', 150)
2123         self.grid.addWidget(self.wids['MRHeader_Type'], 0, 1)
2124         self.grid.addWidget(self.wids['MRHeader_Count'], 0, 2)
2125         self.grid.addWidget(self.wids['MRHeader_Profit'], 0, 3)
2126         self.grid.addWidget(self.wids['MRHeader_PPS'], 0, 4)
2127 
2128         for i in range(0, 9):
2129             self.wids[i] = {}
2130             self.wids[i]['Type'] = QLabelA('', 'White-Square-Table')
2131             self.wids[i]['Count'] = QLabelA('', 'White-Square-Table')
2132             self.wids[i]['Profit'] = QLabelA('', 'White-Square-Table')
2133             self.wids[i]['PPS'] = QLabelA('', 'White-Square-Table')
2134 
2135             self.grid.addWidget(self.wids[i]['Type'], i + 1, 1)
2136             self.grid.addWidget(self.wids[i]['Count'], i + 1, 2)
2137             self.grid.addWidget(self.wids[i]['Profit'], i + 1, 3)
2138             self.grid.addWidget(self.wids[i]['PPS'], i + 1, 4)
2139 
2140         self.contents.setLayout(self.grid)
2141 
2142         self.reset()
2143 
2144     def reset(self):
2145         for i in range(0, 9):
2146             self.wids[i]['Type'].setText('')
2147             self.wids[i]['Count'].setText('')
2148             self.wids[i]['Profit'].setText('')
2149             self.wids[i]['PPS'].setText('')
2150 
2151 
2152 class frameRateMenu(baseMenuFrame):
2153     def __init__(self, parent=None, main=None):
2154         super(frameRateMenu, self).__init__(parent, main)
2155 
2156         # Set Menu Parameters
2157         self.setMaximumHeight(800)
2158         self.setMinimumHeight(700)
2159         self.titleL.setText('Frame Rate Analysis\n(%s Seconds Intervals)' % FRAME_RATE_ANALYSIS_FREQ)
2160 
2161         # Set Grid Contents
2162         layout = QtWidgets.QVBoxLayout()
2163 
2164         self.grid = QtWidgets.QGridLayout()
2165         self.grid.setSpacing(0)
2166         self.grid.setVerticalSpacing(0)
2167         self.grid.setContentsMargins(50, 50, 50, 50)
2168         self.grid.setAlignment(QtCore.Qt.AlignCenter)
2169 
2170         self.wids = {}
2171 
2172         self.wids['MRHeader_Frames'] = QLabelA('Frames', 'White-Square-Table-Title', 150)
2173         self.wids['MRHeader_Setpoint'] = QLabelA('Setpoint (ms)', 'White-Square-Table-Title', 150)
2174         self.wids['MRHeader_Target'] = QLabelA('High Limit', 'White-Square-Table-Title', 150)
2175         self.wids['MRHeader_High'] = QLabelA('High Frames', 'White-Square-Table-Title', 150)
2176         self.wids['MRHeader_Highest'] = QLabelA('Highest', 'White-Square-Table-Title', 150)
2177         self.wids['MRHeader_FPS'] = QLabelA('Average FPS', 'White-Square-Table-Title', 150)
2178         self.grid.addWidget(self.wids['MRHeader_Frames'], 0, 0)
2179         self.grid.addWidget(self.wids['MRHeader_Setpoint'], 0, 1)
2180         self.grid.addWidget(self.wids['MRHeader_Target'], 0, 2)
2181         self.grid.addWidget(self.wids['MRHeader_High'], 0, 3)
2182         self.grid.addWidget(self.wids['MRHeader_Highest'], 0, 4)
2183         self.grid.addWidget(self.wids['MRHeader_FPS'], 0, 5)
2184 
2185         self.wids['Frames'] = QLabelA('-', 'White-Square-Table')
2186         self.wids['Setpoint'] = QLabelA('-', 'White-Square-Table')
2187         self.wids['High Limit'] = QLabelA('-', 'White-Square-Table')
2188         self.wids['High'] = QLabelA('-', 'White-Square-Table')
2189         self.wids['Highest'] = QLabelA('-', 'White-Square-Table')
2190         self.wids['AverageFPS'] = QLabelA('-', 'White-Square-Table')
2191 
2192         self.grid.addWidget(self.wids['Frames'], 1, 0)
2193         self.grid.addWidget(self.wids['Setpoint'], 1, 1)
2194         self.grid.addWidget(self.wids['High Limit'], 1, 2)
2195         self.grid.addWidget(self.wids['High'], 1, 3)
2196         self.grid.addWidget(self.wids['Highest'], 1, 4)
2197         self.grid.addWidget(self.wids['AverageFPS'], 1, 5)
2198 
2199         self.figure = plt.figure()
2200         self.canvas = FigureCanvas(self.figure)
2201         self.canvas.setContentsMargins(40, 40, 40, 40)
2202         layout.addWidget(self.canvas)
2203         layout.addLayout(self.grid)
2204         self.contents.setLayout(layout)
2205 
2206         self.reset()
2207 
2208     def plot(self):
2209         data = self.main.frameRateResultSet
2210         self.figure.clear()
2211         ax = self.figure.add_subplot(111)
2212         ax.plot(data, 'o', markersize=3)
2213         numFrames = FRAME_RATE_ANALYSIS_FREQ * (1000 / CYCLE_INTERVAL)
2214         ax.plot([0, numFrames], [CYCLE_INTERVAL, CYCLE_INTERVAL], color='r', linestyle='-', linewidth=1)
2215         ax.plot([0, numFrames], [int(CYCLE_INTERVAL * 1.5), int(CYCLE_INTERVAL * 1.5)], color='r', linestyle='-',
2216                 linewidth=1)
2217 
2218         ax.set_xlabel('Frame Number')
2219         ax.set_ylabel('Time (ms)')
2220         ax.set_ylim([0, 160])
2221         self.canvas.draw()
2222 
2223     def reset(self):
2224         for i in range(0, 9):
2225             self.wids['Frames'].setText('-')
2226             self.wids['High Limit'].setText('-')
2227             self.wids['High'].setText('-')
2228             self.wids['Highest'].setText('-')
2229 
2230 
2231 class achievementsMenu(scrollingBaseMenuFrame):
2232     def __init__(self, parent=None, main=None):
2233         super(achievementsMenu, self).__init__(parent, main)
2234 
2235         # Set Menu Parameters
2236         self.setMinimumWidth(1000)
2237         self.titleL.setText('Achievements Unlocked')
2238 
2239         # Set Grid Contents
2240         self.grid = QtWidgets.QGridLayout()
2241         self.grid.setSpacing(6)
2242         self.grid.setContentsMargins(40, 40, 40, 40)
2243 
2244         # Populate list of achievements
2245         self.wids = {}
2246         for i, item in enumerate(list(self.main.achievementLib.lib) + list(self.main.materialLib.lib)):
2247             self.wids[item] = {}
2248             self.wids[item]['name'] = QLabelA(item, 'White-None', None, 14)
2249 
2250             if item in self.main.materialLib.lib:
2251                 self.wids[item]['desc'] = QLabelA('Sell one %s' % item, 'White-None')
2252             else:
2253                 self.wids[item]['desc'] = QLabelA(self.main.achievementLib.lib[item]['description'], 'White-None')
2254 
2255             self.wids[item]['cellFrame'] = QtWidgets.QFrame()
2256             self.wids[item]['cellFrame'].setFixedWidth(280)
2257             self.wids[item]['cellFrame'].setStyleSheet('background-color: white;\ 
2258                                                        border: 1px solid black;\ 
2259                                                        border-radius: 6;')
2260             cellVBox = QtWidgets.QVBoxLayout(self.wids[item]['cellFrame'])
2261             cellVBox.addWidget(self.wids[item]['name'])
2262             cellVBox.addWidget(self.wids[item]['desc'])
2263 
2264             self.grid.addWidget(self.wids[item]['cellFrame'], i // 3, i % 3 + 1)
2265 
2266         self.grid.setColumnStretch(0, 1)
2267         self.grid.setColumnStretch(5, 1)
2268 
2269         self.contents.setLayout(self.grid)
2270 
2271         self.reset()
2272 
2273     def reset(self):
2274         for item in self.main.achievementLib.lib:
2275             if item in self.main.unlockedAchievements:
2276                 self.wids[item]['cellFrame'].setStyleSheet('background-color: green;\ 
2277                                                             border: 1px solid black;\ 
2278                                                             border-radius: 6;')
2279                 self.wids[item]['name'].setStyleCode('Green-None')
2280                 self.wids[item]['desc'].setStyleCode('Green-None')
2281             else:
2282                 self.wids[item]['cellFrame'].setStyleSheet('background-color: white;\ 
2283                                                             border: 1px solid black;\ 
2284                                                             border-radius: 6;')
2285                 self.wids[item]['name'].setStyleCode('White-None')
2286                 self.wids[item]['desc'].setStyleCode('White-None')
2287 
2288         for item in self.main.materialLib.lib:
2289             if item in self.main.unlockedAchievements:
2290                 self.wids[item]['cellFrame'].setStyleSheet('background-color: green;\ 
2291                                                             border: 1px solid black;\ 
2292                                                             border-radius: 6;')
2293                 self.wids[item]['name'].setStyleCode('Green-None')
2294                 self.wids[item]['desc'].setStyleCode('Green-None')
2295             else:
2296                 self.wids[item]['cellFrame'].setStyleSheet('background-color: white;\ 
2297                                                             border: 1px solid black;\ 
2298                                                             border-radius: 6;')
2299                 self.wids[item]['name'].setStyleCode('White-None')
2300                 self.wids[item]['desc'].setStyleCode('White-None')
2301 
2302 
2303 class floorPlanMenu(baseMenuFrame):
2304     def __init__(self, parent=None, main=None):
2305         super(floorPlanMenu, self).__init__(parent, main)
2306 
2307         # Set Menu Parameters
2308         self.setMaximumHeight(600)
2309         self.titleL.setText('Floor Plans')
2310 
2311         # Set Grid Contents
2312         self.grid = QtWidgets.QGridLayout()
2313         self.grid.setSpacing(0)
2314         self.grid.setVerticalSpacing(0)
2315         self.grid.setContentsMargins(50, 50, 50, 50)
2316         self.grid.setColumnStretch(0, 1)
2317         self.grid.setColumnStretch(7, 1)
2318 
2319         self.wids = {}
2320 
2321         self.wids['headerName'] = QLabelA('Number', 'White-Square-Table-Title', 100)
2322         self.wids['headerDesc'] = QLabelA('Description', 'White-Square-Table-Title', 300)
2323         self.wids['headerSize'] = QLabelA('Size', 'White-Square-Table-Title', 100)
2324         self.wids['name'] = QLabelA('Name', 'White-Square-Table-Title', 80)
2325         self.wids['buy'] = QLabelA('Buy', 'White-Square-Table-Title', 80)
2326         self.wids['create'] = QLabelA('Create', 'White-Square-Table-Title', 80)
2327 
2328         self.grid.addWidget(self.wids['headerName'], 0, 1)
2329         self.grid.addWidget(self.wids['headerDesc'], 0, 2)
2330         self.grid.addWidget(self.wids['headerSize'], 0, 3)
2331         self.grid.addWidget(self.wids['name'], 0, 4)
2332         self.grid.addWidget(self.wids['buy'], 0, 5)
2333         self.grid.addWidget(self.wids['create'], 0, 6)
2334 
2335         # Add Lock Widget Over Plans
2336         self.floorPlanFeatureLock = QLabelA('Research Floor Plans to Unlock', 'Gray-Square')
2337         self.grid.addWidget(self.floorPlanFeatureLock, 0, 0, 11, 8)
2338         self.floorPlanFeatureLock.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
2339 
2340         for i in range(0, 10):  # 10 plans, 0 thru 9
2341             self.wids[i] = {}
2342             self.wids[i]['num'] = QLabelA('-', 'White-Square-Table')
2343             self.wids[i]['name'] = QLabelA('-', 'White-Square-Table')
2344             self.wids[i]['entry'] = QtWidgets.QLineEdit(self.main.floorPlans[i]['description'])
2345             self.wids[i]['size'] = QLabelA('-', 'White-Square-Table')
2346             self.wids[i]['save'] = QPushButtonA('Save', 'Blue-Square', 80)
2347             self.wids[i]['edit'] = QPushButtonA('Edit', 'White-Square-Table', 80)
2348             self.wids[i]['place'] = QPushButtonA('Place', 'White-Square-Table', 80)
2349             self.wids[i]['new'] = QPushButtonA('New', 'White-Square-Table', 80)
2350 
2351             self.wids[i]['entry'].setMinimumHeight(24)
2352             self.wids[i]['edit'].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
2353             self.wids[i]['place'].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
2354             self.wids[i]['new'].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
2355 
2356             # self.wids[i]['name'].setAlignment(QtCore.Qt.AlignLeft)
2357             self.wids[i]['entry'].setStyleSheet('QLineEdit{background-color: steelblue;\ 
2358                                                 border: 1px solid black;\ 
2359                                                 color: white;}')
2360 
2361             self.wids[i]['edit'].clicked.connect(lambda state, i=i: self.editFloorPlanName(i))
2362             self.wids[i]['new'].clicked.connect(lambda state, i=i: self.main.newFloorPlanSelTopLeftMode(i))
2363             self.wids[i]['place'].clicked.connect(lambda state, i=i: self.main.placeFloorPlanSelTopLeftMode(i))
2364             self.wids[i]['save'].clicked.connect(lambda state, i=i: self.saveFloorPlanName(i))
2365 
2366             self.grid.addWidget(self.wids[i]['num'], i + 1, 1)
2367             self.grid.addWidget(self.wids[i]['entry'], i + 1, 2)
2368             self.grid.addWidget(self.wids[i]['name'], i + 1, 2)
2369             self.grid.addWidget(self.wids[i]['size'], i + 1, 3)
2370             self.grid.addWidget(self.wids[i]['save'], i + 1, 4)
2371             self.grid.addWidget(self.wids[i]['edit'], i + 1, 4)
2372             self.grid.addWidget(self.wids[i]['place'], i + 1, 5)
2373             self.grid.addWidget(self.wids[i]['new'], i + 1, 6)
2374 
2375         self.contents.setLayout(self.grid)
2376 
2377         self.reset()
2378 
2379     def reset(self):
2380         for i in range(0, 10):
2381             self.wids[i]['num'].setText(str(i + 1))
2382             self.wids[i]['name'].setText('-')
2383             self.wids[i]['size'].setText('-')
2384 
2385         for i, (plan, value) in enumerate(self.main.floorPlans.items()):
2386             self.wids[i]['name'].setText('%s' % self.main.floorPlans[plan]['description'])
2387             self.wids[i]['size'].setText('%i x %i' % self.main.floorPlans[i]['size'])
2388 
2389         if 'floorPlansFeatureUnlock' in self.main.unlockedResearch:
2390             self.floorPlanFeatureLock.hide()
2391         else:
2392             self.floorPlanFeatureLock.show()
2393             self.floorPlanFeatureLock.raise_()
2394 
2395     def editFloorPlanName(self, i):
2396         self.wids[i]['save'].raise_()
2397         self.wids[i]['entry'].raise_()
2398         self.wids[i]['entry'].setFocus()
2399 
2400     def saveFloorPlanName(self, i):
2401         self.wids[i]['save'].lower()
2402         self.wids[i]['entry'].lower()
2403         self.main.floorPlans[i]['description'] = self.wids[i]['entry'].text()
2404         self.wids[i]['name'].setText(self.main.floorPlans[i]['description'])
2405 
2406 
2407 class achievementPopUpMenu(QtWidgets.QFrame):
2408     def __init__(self, parent=None, main=None):
2409         super(achievementPopUpMenu, self).__init__(parent)
2410         self.parent = parent
2411         self.main = main
2412 
2413         # Frame Setup
2414         self.setMaximumHeight(140)
2415         self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
2416         self.setStyleSheet('QFrame{background-color: green;\ 
2417                            border: 1px solid black;\ 
2418                            border-radius: 6;}')
2419         self.setMinimumWidth(400)
2420         self.wids = {}
2421 
2422         # Grid Contents
2423         self.grid = QtWidgets.QGridLayout()
2424         self.grid.setSpacing(10)
2425         self.grid.setContentsMargins(20, 20, 20, 20)
2426 
2427         self.achievementName = QLabelA('Achievement Name', 'Green-None', 300)
2428         self.grid.addWidget(self.achievementName, 0, 0)
2429 
2430         # Frame's Layout
2431         self.setLayout(self.grid)
2432 
2433         self.reset()
2434 
2435     def reset(self):
2436         self.achievementName.setText('None')
2437 
2438 
2439 # -------- MainApp Class -------- #
2440 
2441 class clsMainApp(QtWidgets.QMainWindow):
2442     def __init__(self, parent=None):
2443         super(clsMainApp, self).__init__(parent)
2444 
2445         # MainApp GUI Structure:
2446         # mainApp (QMainWindow)
2447         #    mainWidget (QWidget)[mainVLayout] *centralWidget*
2448         #      Top Dock Widget(QWidget)
2449         #      container (QWidget)  - [containerGrid (QGridLayout(1x1))]
2450         #          viewFrame (QGraphicsView)
2451         #          buildMenuFrame (QFrame)
2452         #          blueprintsMenuFrame (QFrame)
2453         #          etc...
2454         #      Bottom Dock Widget(QWidget)
2455 
2456         print('\nRunning...')
2457 
2458         self.Tiles = []  # List of all Tiles
2459         self.Machines = []  # List of all Machines
2460         self.oMachines = []  # List of all Machines frozen at start of each iteration
2461         self.Materials = []  # List of all Materials
2462         self.oMaterials = []  # List of all Materials frozen at start of each iteration
2463         self.iteration = 0  # Initialize iteration
2464         self.groupIDList = {}  # Dict of all group ID's and their materials
2465         self.iterationStartTime = datetime.datetime.now()
2466         self.messageTimer = 0
2467         self.eventTimer = 0
2468         self.achievementTimer = 0
2469         self.achievementPop = None
2470         self.queueReset = False  # Initialize queue reset flag
2471         self.balance = None  # Initialize balance
2472         self.tilePrice = 1000  # Initialize price per tile
2473         self.teleporterInputIDs = {}  # ID, [Objects with that ID]
2474         self.teleporterOutputIDs = {}  # ID, [Objects with that ID]
2475         self.machineLib = machineLib()
2476         self.materialLib = materialLib()
2477         self.researchLib = researchLib()
2478         self.achievementLib = achievementLib()
2479         self.imageLib = imageLib()
2480         self.xClick = None
2481         self.yClick = None
2482         self.xClickTileCenter = None
2483         self.yClickTileCenter = None
2484         self.xCursorTileCenter = None
2485         self.yCursorTileCenter = None
2486         self.selFloorPlan = None
2487         self.floorPlanTopLeft = None
2488         self.floorPlanBottomRight = None
2489         self.thetaAB = None
2490         self.thetaBC = None
2491         self.xRelB = None
2492         self.yRelB = None
2493         self.xRelC = None
2494         self.yRelC = None
2495         self.xRelLinkCenterAB = None
2496         self.yRelLinkCenterAB = None
2497         self.xRelLinkCenterBC = None
2498         self.yRelLinkCenterBC = None
2499         self.angledPixmapLink1 = None
2500         self.angledPixmapLink2 = None
2501         self.timer = None
2502         self.updateNewFloorPlanVisualsFlag = False
2503         self.updatePlaceFloorPlanVisualsFlag = False
2504         self.floorPlans = {}
2505         for i in range(0, 10):  # Plans # 0 to 9
2506             self.floorPlans[i] = {}
2507             self.floorPlans[i]['size'] = (0, 0)
2508             self.floorPlans[i]['description'] = ''
2509             self.floorPlans[i]['machines'] = {}
2510         self.unlockedMachines = []
2511         self.unlockedBlueprints = []
2512         self.unlockedTiles = []
2513         self.unlockedAssyLines = []
2514         self.unlockedAchievements = []
2515         self.unlockedResearch = []
2516         self.opCostModifier = None  # Research modifier variables
2517         self.maxStarters = None
2518         self.maxTeleporters = None
2519         self.opTimeModifierStarterCrafter = None
2520         self.opTimeModifierTier2Machines = None
2521         self.starterMaxSpawnQuantity = None
2522         self.machineToBeMoved = None
2523         self.moneyRate = 0  # Money analysis variables
2524         self.itemRate = 0
2525         self.lastMRAnalysisTime = datetime.datetime.now()
2526         self.salesCollector = {}
2527         self.salesAnalysis = {}
2528         self.clickedTool = None
2529         self.clickedTile = None
2530         self.frameRateResultSet = []  # Frame rate analysis variables
2531         self.lastFrameRateAnalysisTime = datetime.datetime.now()
2532 
2533         self.machineBlueprintList = {}  # Pre-computed dictionary of considered blueprints for each machine type
2534         for type in [DRAWER, CUTTER, FURNACE, PRESS]:
2535             self.machineBlueprintList[type] = []
2536             for material in self.materialLib.lib:
2537                 if self.materialLib.lib[material]['class'] == self.machineLib.lib[type]['blueprintType']:
2538                     self.machineBlueprintList[type].append(material)
2539 
2540         # Menu Geometry Setup
2541         self.sceneWidth = 1250  # Scene Width
2542         self.sceneHeight = 400  # Scene Height
2543         self.appWidth = 1400  # Overall window width
2544         self.appHeight = 600  # Overall window height
2545 
2546         # App Icon
2547         self.setWindowIcon(QtGui.QIcon('images/icon.png'))
2548 
2549         # Menu Bar
2550         mainMenu = self.menuBar()
2551 
2552         saveAction = QtWidgets.QAction("&Save", self)
2553         saveAction.setShortcut("Ctrl+S")
2554         saveAction.setStatusTip('Save Game - Ctrl+S')
2555         saveAction.triggered.connect(self.saveConfig)
2556 
2557         zoomInAction = QtWidgets.QAction("Zoom In", self)
2558         zoomInAction.setShortcut("Ctrl++")
2559         zoomInAction.setStatusTip('Zoom In - Ctrl+I')
2560         zoomInAction.triggered.connect(lambda: self.viewFrameZoom(IN))
2561 
2562         zoomOutAction = QtWidgets.QAction("Zoom Out", self)
2563         zoomOutAction.setShortcut("Ctrl+-")
2564         zoomOutAction.setStatusTip('Load Game - Ctrl+O')
2565         zoomOutAction.triggered.connect(lambda: self.viewFrameZoom(OUT))
2566 
2567         zoomResetAction = QtWidgets.QAction("Zoom Reset", self)
2568         zoomResetAction.setShortcut("Ctrl+Z")
2569         zoomResetAction.setStatusTip('Reset Zoom - Ctrl+Z')
2570         zoomResetAction.triggered.connect(lambda: self.viewFrameZoom(RESET))
2571 
2572         loadAction = QtWidgets.QAction("&Load", self)
2573         loadAction.setShortcut("Ctrl+L")
2574         loadAction.setStatusTip('Load Game - Ctrl+L')
2575         loadAction.triggered.connect(self.loadConfig)
2576 
2577         resetAction = QtWidgets.QAction("&Reset", self)
2578         resetAction.setShortcut("Ctrl+R")
2579         resetAction.setStatusTip('Reset Game - Ctrl+R')
2580         resetAction.triggered.connect(self.reset)
2581 
2582         cancelAction = QtWidgets.QAction("Cancel", self)
2583         cancelAction.setShortcut("ESCAPE")
2584         cancelAction.setStatusTip('Cancel - Escape')
2585         cancelAction.triggered.connect(self.closeMode)
2586 
2587         exitAction = QtWidgets.QAction('&Exit', self)
2588         exitAction.setShortcut('Ctrl+W')
2589         exitAction.setStatusTip('Exit - Ctrl+W')
2590         exitAction.triggered.connect(sys.exit)
2591 
2592         mainMenu.addAction(saveAction)
2593         mainMenu.addAction(zoomInAction)
2594         mainMenu.addAction(zoomOutAction)
2595         mainMenu.addAction(zoomResetAction)
2596         mainMenu.addAction(loadAction)
2597         mainMenu.addAction(resetAction)
2598         mainMenu.addAction(cancelAction)
2599         mainMenu.addAction(exitAction)
2600 
2601         # Main Window Setup
2602         self.setStyleSheet('QMainWindow{background-color: white}')
2603         self.setWindowTitle('Assembly Line')
2604         self.scene = QtWidgets.QGraphicsScene(self)
2605 
2606         self.statusBar = QtWidgets.QStatusBar()
2607         self.setStatusBar(self.statusBar)
2608         self.statusBar.setMinimumHeight(30)
2609         self.statusBar.setStyleSheet('background-color: white; border-top: 1px solid black')
2610         font = QtGui.QFont()
2611         font.setPointSize(10)
2612         self.statusBar.setFont(font)
2613         self.statusBar.showMessage('None')
2614 
2615         self.resize(self.appWidth, self.appHeight)
2616 
2617         self.singleSW, self.singleSH, self.combinedSW, self.combinedSH = self.getSystemInfo()
2618         if self.combinedSW < 2600:  # Single screen detected
2619             self.move(int((self.singleSW - self.appWidth) / 2), int((self.singleSH - self.appHeight) / 2 - 300))
2620         else:  # Dual screen detected
2621             self.move(int((self.singleSW - self.appWidth) / 2 + 1900), int((self.singleSH - self.appHeight) / 2 - 240))
2622 
2623         # Widget Setup
2624         self.wids = {}
2625         self.db = {}
2626         self.dbfile = None
2627 
2628         self.coreLoop = clsCoreLoop(self)  # Instantiate the coreLoop
2629 
2630         # Draw Grid Lines
2631         for i in range(0, 400 + 1, 25):  # Create horizontal grid lines
2632             self.scene.addItem(QtWidgets.QGraphicsLineItem(0, i, 1250, i))
2633         for i in range(0, 1250 + 1, 25):  # Create vertical grid lines
2634             self.scene.addItem(QtWidgets.QGraphicsLineItem(i, 0, i, 400))
2635 
2636         # Header Dock Widget
2637         self.headerDockWidget = QtWidgets.QFrame()
2638         self.headerDockWidget.setMinimumHeight(70)
2639         self.headerDockWidget.setMinimumHeight(70)
2640         self.headerDockWidget.setStyleSheet('QFrame{background: steelblue}')
2641         self.grid = QtWidgets.QGridLayout()
2642         self.grid.setSpacing(2)
2643         self.grid.setColumnMinimumWidth(1, 5)
2644 
2645         self.appTitle = QLabelA('Assembly Line', 'Blue-None-White-Large', 400)
2646 
2647         self.grid.setColumnStretch(7, 1)
2648 
2649         self.vbox = QtWidgets.QVBoxLayout()
2650         self.vboxsub1 = QtWidgets.QVBoxLayout()
2651         self.vboxsub2 = QtWidgets.QVBoxLayout()
2652 
2653         self.vboxsub1.addWidget(self.appTitle)
2654         self.vboxsub1.setAlignment(QtCore.Qt.AlignCenter)
2655 
2656         self.vboxsub2.addLayout(self.grid)
2657 
2658         self.vbox.addLayout(self.vboxsub1)
2659         self.vbox.addLayout(self.vboxsub2)
2660 
2661         self.vbox.setAlignment(QtCore.Qt.AlignCenter)
2662 
2663         self.headerDockWidget.setLayout(self.vbox)
2664 
2665         # Upper Dock Widget
2666         self.upperDockWidget = QtWidgets.QFrame()
2667         self.upperDockWidget.setMinimumHeight(90)
2668         self.upperDockWidget.setStyleSheet('QFrame{background: rgb(220,220,220); border-top: 1px solid black}}')
2669         self.grid = QtWidgets.QGridLayout()
2670         self.grid.setSpacing(6)
2671         self.grid.setColumnMinimumWidth(1, 5)
2672 
2673         self.balance_label = QLabelA('Balance:', 'White-Round', 400)
2674         self.metric_label = QLabelA('0 / %i Achievements Unlocked' % self.getAmountOfAchievements(), 'White-Round', 400)
2675         self.moneyRate_label = QLabelA('-', 'White-Round', 400)
2676         self.itemRate_label = QLabelA('-:', 'White-Round', 400)
2677         self.message_label = QLabelA('-', 'White-Round', 800)
2678         self.event_label = QLabelA('-', 'White-Round', 800)
2679 
2680         self.balance_label.setFixedHeight(32)
2681         self.metric_label.setFixedHeight(32)
2682         self.moneyRate_label.setFixedHeight(32)
2683         self.itemRate_label.setFixedHeight(32)
2684         self.message_label.setFixedHeight(32)
2685         self.event_label.setFixedHeight(32)
2686 
2687         self.grid.setColumnStretch(0, 1)
2688         self.grid.addWidget(self.balance_label, 1, 1, 1, 1, QtCore.Qt.AlignCenter)
2689         self.grid.addWidget(self.metric_label, 1, 2, 1, 1, QtCore.Qt.AlignCenter)
2690         self.grid.addWidget(self.moneyRate_label, 1, 3, 1, 1, QtCore.Qt.AlignCenter)
2691         self.grid.addWidget(self.itemRate_label, 1, 4, 1, 1, QtCore.Qt.AlignCenter)
2692         self.grid.addWidget(self.message_label, 2, 1, 1, 2, QtCore.Qt.AlignCenter)
2693         self.grid.addWidget(self.event_label, 2, 3, 1, 2, QtCore.Qt.AlignCenter)
2694         self.grid.setColumnStretch(5, 1)
2695 
2696         self.upperDockWidget.setLayout(self.grid)
2697 
2698         # Lower Dock Widget
2699         self.lowerDockWidget = QtWidgets.QFrame()
2700         self.lowerDockWidget.setStyleSheet('QFrame{background: rgb(220,220,220)}')
2701         self.grid = QtWidgets.QGridLayout()
2702         self.grid.setSpacing(0)
2703 
2704         self.move_button = QPushButtonA('Move', 'White-Square-Menu-Left-Side', 150)
2705         self.rotate_button = QPushButtonA('Rotate', 'White-Square-Menu-Left-Side', 150)
2706         self.sell_button = QPushButtonA('Sell', 'White-Square-Menu-Left-Side', 150)
2707         self.tiles_button = QPushButtonA('Purchase Tiles', 'White-Square-Menu-Left-Side', 150)
2708         self.cancel_button = QPushButtonA('Cancel', 'White-Square-Menu-Right-Side', 150)
2709 
2710         self.build_button = QPushButtonA('Build', 'White-Square-Menu-Left-Side', 150)
2711         self.blueprints_button = QPushButtonA('Blueprints', 'White-Square-Menu-Left-Side', 150)
2712         self.research_button = QPushButtonA('Research', 'White-Square-Menu-Left-Side', 150)
2713         self.buyLine_button = QPushButtonA('Buy Assy Lines', 'White-Square-Menu-Left-Side', 150)
2714         self.analysis_button = QPushButtonA('Income', 'White-Square-Menu-Right-Side', 150)
2715 
2716         self.achievements_button = QPushButtonA('Achievements', 'White-Square-Menu-Bottom-Side', 150)
2717         self.help_button = QPushButtonA('Help', 'White-Square-Menu-Bottom-Side', 150)
2718         self.floorPlan_button = QPushButtonA('Floor Plans', 'White-Square-Menu-Bottom-Side', 150)
2719         self.frameRate_button = QPushButtonA('FPS (  )', 'White-Square-Menu-Bottom-Side', 150)
2720         self.debug_button = QPushButtonA('Debug', 'White-Square', 150)
2721 
2722         self.grid.setColumnStretch(0, 1)
2723 
2724         self.grid.addWidget(self.move_button, 1, 1)
2725         self.grid.addWidget(self.rotate_button, 1, 2)
2726         self.grid.addWidget(self.sell_button, 1, 3)
2727         self.grid.addWidget(self.tiles_button, 1, 4)
2728         self.grid.addWidget(self.cancel_button, 1, 5)
2729 
2730         self.grid.addWidget(self.build_button, 2, 1)
2731         self.grid.addWidget(self.blueprints_button, 2, 2)
2732         self.grid.addWidget(self.research_button, 2, 3)
2733         self.grid.addWidget(self.buyLine_button, 2, 4)
2734         self.grid.addWidget(self.analysis_button, 2, 5)
2735 
2736         self.grid.addWidget(self.achievements_button, 3, 1)
2737         self.grid.addWidget(self.help_button, 3, 2)
2738         self.grid.addWidget(self.floorPlan_button, 3, 3)
2739         self.grid.addWidget(self.frameRate_button, 3, 4)
2740         self.grid.addWidget(self.debug_button, 3, 5)
2741 
2742         self.grid.setColumnStretch(9, 1)
2743 
2744         self.move_button.clicked.connect(self.moveMode)
2745         self.rotate_button.clicked.connect(self.rotateMode)
2746         self.sell_button.clicked.connect(self.sellMode)
2747         self.tiles_button.clicked.connect(self.buyTilesMode)
2748         self.cancel_button.clicked.connect(self.closeMode)
2749         self.build_button.clicked.connect(lambda: self.openMenu(self.buildMenuFrame))
2750         self.blueprints_button.clicked.connect(lambda: self.openMenu(self.blueprintsMenuFrame))
2751         self.research_button.clicked.connect(lambda: self.openMenu(self.researchMenuFrame))
2752         self.buyLine_button.clicked.connect(lambda: self.openMenu(self.assyLineMenuFrame))
2753         self.analysis_button.clicked.connect(lambda: self.openMenu(self.incomeAnalysisMenuFrame))
2754         self.achievements_button.clicked.connect(lambda: self.openMenu(self.achievementsMenuFrame))
2755         self.help_button.clicked.connect(lambda: self.openMenu(self.helpMenuFrame))
2756         self.floorPlan_button.clicked.connect(lambda: self.openMenu(self.floorPlanMenuFrame))
2757         self.frameRate_button.clicked.connect(lambda: self.openMenu(self.frameRateMenuFrame))
2758         self.debug_button.clicked.connect(self.debugMode)
2759 
2760         self.lowerDockWidget.setLayout(self.grid)
2761 
2762         # Create Central Container Widget
2763         self.container = QtWidgets.QWidget()
2764         self.container.setContentsMargins(10, 10, 10, 10)
2765         self.container.setObjectName('container')  # Name stylesheet to prevent inherited style
2766         self.container.setStyleSheet('#container{background-color: rgb(240,240,240);\ 
2767                                       border: 1px solid black}')
2768         self.container.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
2769         self.containerGrid = QtWidgets.QGridLayout()
2770         self.containerGrid.setColumnStretch(0, 1)
2771         self.containerGrid.setRowStretch(0, 1)
2772 
2773         # Initiate View Menu Frames
2774         self.view = QtWidgets.QGraphicsView(self.scene)
2775         self.view.setFixedSize(1300, 440)
2776         self.viewFrame = QtWidgets.QFrame()
2777         self.viewFrame.setContentsMargins(10, 10, 10, 10)
2778         self.viewFrame.setStyleSheet('background-color: white;\ 
2779                                       border: 1px solid black}')
2780         self.viewFrameVBox = QtWidgets.QVBoxLayout()
2781         self.viewFrameVBox.setAlignment(QtCore.Qt.AlignCenter)
2782         self.viewFrameVBox.addStretch(1)
2783         self.viewFrameVBox.addWidget(self.view)
2784         self.viewFrameVBox.addStretch(1)
2785         self.viewFrame.setLayout(self.viewFrameVBox)
2786 
2787         # Zoom Settings
2788         self.scaleFactor = 1
2789         self.view.scale(self.scaleFactor, self.scaleFactor)
2790 
2791         # For mousing position tracking for highlight tile visuals
2792         self.scene.installEventFilter(self)
2793 
2794         # Initiate Menu Frames
2795         self.buildMenuFrame = buildMenu(main=self)
2796         self.blueprintsMenuFrame = blueprintsMenu(main=self)
2797         self.researchMenuFrame = researchMenu(main=self)
2798         self.assyLineMenuFrame = assyLineMenu(main=self)
2799         self.incomeAnalysisMenuFrame = incomeAnalysisMenu(main=self)
2800         self.frameRateMenuFrame = frameRateMenu(main=self)
2801         self.achievementsMenuFrame = achievementsMenu(main=self)
2802         self.helpMenuFrame = helpMenu(main=self)
2803         self.floorPlanMenuFrame = floorPlanMenu(main=self)
2804         self.toolPropertiesFrame = toolPropertiesMenu(main=self)  # One window for many tools
2805         self.blueprintSelectFrame = blueprintSelectMenu(main=self)  # One window for many tools
2806         self.filterSelectFrame = filterSelectMenu(main=self)  # One window for many tools
2807         self.achievementPopUpMenuFrame = achievementPopUpMenu(main=self)  # Only achievement pop-up frame
2808 
2809         self.containerGrid.addWidget(self.viewFrame, 0, 0)  # Don't align center if want to fill space
2810         self.containerGrid.addWidget(self.buildMenuFrame, 0, 0, QtCore.Qt.AlignHCenter)
2811         self.containerGrid.addWidget(self.blueprintsMenuFrame, 0, 0, QtCore.Qt.AlignHCenter)
2812         self.containerGrid.addWidget(self.researchMenuFrame, 0, 0, QtCore.Qt.AlignHCenter)
2813         self.containerGrid.addWidget(self.assyLineMenuFrame, 0, 0, QtCore.Qt.AlignHCenter)
2814         self.containerGrid.addWidget(self.achievementsMenuFrame, 0, 0, QtCore.Qt.AlignHCenter)
2815         self.containerGrid.addWidget(self.incomeAnalysisMenuFrame, 0, 0, QtCore.Qt.AlignHCenter)
2816         self.containerGrid.addWidget(self.frameRateMenuFrame, 0, 0, QtCore.Qt.AlignHCenter)
2817         self.containerGrid.addWidget(self.helpMenuFrame, 0, 0, QtCore.Qt.AlignHCenter)
2818         self.containerGrid.addWidget(self.floorPlanMenuFrame, 0, 0, QtCore.Qt.AlignHCenter)
2819         self.containerGrid.addWidget(self.toolPropertiesFrame, 0, 0, QtCore.Qt.AlignHCenter)
2820         self.containerGrid.addWidget(self.blueprintSelectFrame, 0, 0, QtCore.Qt.AlignHCenter)
2821         self.containerGrid.addWidget(self.filterSelectFrame, 0, 0, QtCore.Qt.AlignHCenter)
2822         self.containerGrid.addWidget(self.achievementPopUpMenuFrame, 0, 0, QtCore.Qt.AlignHCenter)
2823 
2824         # Set Main Layout
2825         self.mainVLayout = QtWidgets.QVBoxLayout()
2826         self.mainVLayout.setSpacing(0)
2827         self.mainVLayout.setContentsMargins(0, 0, 0, 0)
2828         self.mainVLayout.addWidget(self.headerDockWidget)
2829         self.mainVLayout.addWidget(self.upperDockWidget)
2830         self.mainVLayout.addWidget(self.container)
2831         self.mainVLayout.addWidget(self.lowerDockWidget)
2832 
2833         self.mainWidget = QtWidgets.QWidget()
2834         self.mainWidget.setLayout(self.mainVLayout)
2835 
2836         self.container.setLayout(self.containerGrid)
2837         self.raiseFrame(self.viewFrame)
2838         self.setCentralWidget(self.mainWidget)
2839         self.show()
2840         # self.showMaximized()
2841 
2842         self.scene.mouseReleaseEvent = lambda event: self.showToolProperties(event)
2843 
2844     def raiseFrame(self, frame):
2845         self.viewFrame.raise_()
2846         frame.raise_()
2847 
2848     @staticmethod
2849     def lowerFrame(frame):
2850         frame.lower()
2851 
2852     def openMenu(self, frame):
2853         self.closeMode()
2854         self.raiseFrame(frame)
2855 
2856     def closeMenus(self):
2857         self.viewFrame.raise_()
2858         self.delAllArrows()
2859         self.toolPropertiesFrame.selectedTool = None
2860 
2861     def eventFilter(self, source, event):
2862         if event.type() == QtCore.QEvent.GraphicsSceneMouseMove:  # Not all events are mousemove with pos()
2863             if self.updateNewFloorPlanVisualsFlag is True:
2864                 self.updateNewFloorPlanVisuals(event)  # Highlights selected tiles
2865             if self.updatePlaceFloorPlanVisualsFlag is True:
2866                 self.updatePlaceFloorPlanVisuals(event)  # Highlights selected tiles
2867         return super(clsMainApp, self).eventFilter(source, event)
2868 
2869     def showToolProperties(self, event):
2870         self.clicked(event)
2871         if self.clickedTool:
2872             self.toolPropertiesFrame.displayInfo(self.clickedTool)
2873 
2874     def viewFrameZoom(self, direction):
2875         if direction == IN:
2876             self.view.setTransform(QtGui.QTransform())  # Reset
2877             self.view.scale(2, 2)  # 2.0x
2878         if direction == OUT:
2879             self.view.setTransform(QtGui.QTransform())  # Reset
2880             self.view.scale(0.5, 0.5)  # 0.5x
2881         if direction == RESET:
2882             self.view.setTransform(QtGui.QTransform())  # Reset
2883 
2884     def buildMachineAttempt(self, event, machine):
2885         self.clicked(event)
2886         if not self.clickedTool:
2887             if self.clickedTile is not None \
2888                     and not self.clickedTile.locked \
2889                     and not self.clickedTile.walled \
2890                     and self.clickedTool is None:
2891                 self.buildMachineIfAbleToBuildAny(
2892                     machine, self.clickedTile.x, self.clickedTile.y, 'D', None, 1, None, None, 1)
2893             else:
2894                 self.updateMessage('Invalid Location')
2895 
2896     def buildMachineIfAbleToBuildAny(self, machine, x, y, orientation, selectedBlueprint, starterQuantity, filterLeft,
2897                                      filterRight, teleportID):
2898         # Checks balance & max machine limits
2899         if machine == STARTER and self.maxStarters is not None \
2900                 and self.getNumberMachinesInClickedAssyLine(STARTER) >= self.maxStarters:
2901             self.updateMessage('Maximum amount of Starters (%i) already placed in this line' % self.maxStarters)
2902         elif machine == TELEPORTER_INPUT and self.maxStarters is not None \
2903                 and self.getNumberMachinesInClickedAssyLine(TELEPORTER_INPUT) >= self.maxTeleporters:
2904             self.updateMessage('Maximum amount of Teleporters (%i) already placed in this line' % self.maxTeleporters)
2905         else:
2906             self.Machines.append(clsMachine(self, machine, x, y, orientation,
2907                                             selectedBlueprint, starterQuantity, filterLeft, filterRight, teleportID))
2908             self.updateBalance(self.balance - self.machineLib.lib[machine]['buildCost'])
2909             self.updateMessage(
2910                 'Purchased %s for $%s' % (machine, self.shortNum(self.machineLib.lib[machine]['buildCost'])))
2911 
2912     def buildMode(self, machine):
2913         self.deselectAllButtons()
2914         self.closeMenus()
2915         self.build_button.setStyleCode('Blue-Square-Menu-Left-Side')
2916         self.scene.mouseReleaseEvent = lambda event, machine=machine: self.buildMachineAttempt(event, machine)
2917         self.statusBar.showMessage('Select location to build %s' % machine)
2918 
2919     def moveMode(self):
2920         self.deselectAllButtons()
2921         self.move_button.setStyleCode('Blue-Square-Menu-Left-Side')
2922         self.scene.mouseReleaseEvent = lambda event: self.moveMachineFrom(event)
2923         self.statusBar.showMessage('Select machine to move')
2924         self.updateMessage('Select Machine To Move')
2925 
2926     def moveMachineFrom(self, event):
2927         self.clicked(event)
2928         self.statusBar.showMessage('Select new machine location')
2929         if self.clickedTool:
2930             self.machineToBeMoved = self.clickedTool
2931             self.clickedTile.drawShape(HIGHLIGHT)
2932             self.updateMessage('Select Destination')
2933             self.scene.mouseReleaseEvent = lambda event: self.moveMachineTo(event)
2934         else:
2935             self.statusBar.showMessage('Invalid Selection, Select Machine To Move')
2936 
2937     def moveMachineTo(self, event):
2938         self.clicked(event)
2939         if not self.clickedTile.locked and not self.clickedTile.walled and self.clickedTool is None:
2940             self.machineToBeMoved.moveMachine(self.xClickTileCenter, self.yClickTileCenter)
2941             self.machineToBeMoved = None
2942             self.delAllHighlights()
2943             self.moveMode()
2944         else:
2945             self.statusBar.showMessage('Invalid location, select new location')
2946 
2947     def rotateMode(self):
2948         self.deselectAllButtons()
2949         self.rotate_button.setStyleCode('Blue-Square-Menu-Left-Side')
2950         self.drawAllArrows()
2951         self.scene.mouseReleaseEvent = lambda event: self.rotateMachineAttempt(event)
2952         self.statusBar.showMessage('Select machine to rotate')
2953 
2954     def rotateMachineAttempt(self, event):
2955         self.clicked(event)
2956         if self.clickedTool:
2957             self.clickedTool.rotateMachine()
2958 
2959     def sellMode(self):
2960         self.deselectAllButtons()
2961         self.sell_button.setStyleCode('Blue-Square-Menu-Left-Side')
2962         self.statusBar.showMessage('Select machine to sell')
2963         self.scene.mouseReleaseEvent = lambda event: self.sellMachineAttempt(event)
2964 
2965     def sellMachineAttempt(self, event):
2966         self.clicked(event)
2967         if self.clickedTool:
2968             self.clickedTool.sellMachine()
2969 
2970     def buyTilesMode(self):
2971         self.deselectAllButtons()
2972         self.tiles_button.setStyleCode('Blue-Square-Menu-Left-Side')
2973         self.scene.mouseReleaseEvent = lambda event: self.buyTileAttempt(event)
2974         self.statusBar.showMessage('Purchase a Tile for $%s' % self.shortNum(self.getTilePrice()))
2975 
2976     def buyTileAttempt(self, event):
2977         self.clicked(event)
2978         if self.clickedTile.locked is True and self.clickedTile.walled is False:
2979             if self.balance >= self.getTilePrice():
2980                 self.clickedTile.buyTile()
2981             else:
2982                 self.updateMessage('Not enough money')
2983         else:
2984             self.updateMessage('Tile not available for purchase')
2985 
2986     def getTilePrice(self):
2987         # Tiles     1           80          160         320             480
2988         # Price     15,000      103,000     725,200     35,968,200      1,784,039,000
2989         # From Exponential curve fit of targets, minus free tiles
2990         price = 14620 * math.exp(0.0244 * (len(self.unlockedTiles) - 288))
2991         return int(round(price, -2))  # Ranges from ~$15k to ~$1.5B, rounded to 100
2992 
2993     def closeMode(self):
2994         self.toolPropertiesFrame.selectedTool = None
2995         self.deselectAllButtons()
2996         self.viewFrame.raise_()
2997         self.scene.mouseReleaseEvent = lambda event: self.showToolProperties(event)
2998         self.delAllHighlights()
2999         self.view.setMouseTracking(False)  # Unbind mouse tracking
3000         self.updateNewFloorPlanVisualsFlag = False  # Stop displaying visuals
3001         self.updatePlaceFloorPlanVisualsFlag = False  # Stop displaying visuals
3002         self.statusBar.showMessage('None')
3003 
3004     def debugMode(self):
3005         self.updateBalance(999999999)
3006 
3007     def clicked(self, event):
3008         point = event.buttonDownScenePos(QtCore.Qt.LeftButton)
3009         self.xClick = point.x()
3010         self.yClick = self.sceneHeight - point.y()  # Scene and grid coordinates systems differ
3011         self.xClickTileCenter, self.yClickTileCenter = self.getTileCenter(self.xClick, self.yClick)
3012         self.clickedTile = self.getTile(self.xClickTileCenter, self.yClickTileCenter)
3013         self.clickedTool = self.getMachine(self.xClickTileCenter, self.yClickTileCenter)
3014 
3015     def newFloorPlanSelTopLeftMode(self, planNum):
3016         self.closeMenus()
3017         self.deselectAllButtons()
3018         self.floorPlan_button.setStyleCode('Blue-Square-Menu-Bottom-Side')
3019         self.selFloorPlan = planNum
3020         self.floorPlan_button.setStyleCode('Blue-White-Square-Menu-Bottom-Side')
3021         self.statusBar.showMessage('Select top left corner to start creating floor plan')
3022         for tile in self.Tiles:  # Pre-draw tile highlights & setVisibility
3023             tile.drawShape(HIGHLIGHT)
3024             tile.shape_highlight.setZValue(Z_HIGHLIGHT)
3025             tile.shape_highlight.setVisible(False)
3026         self.scene.mouseReleaseEvent = lambda event: self.newFloorPlanSelBotRightMode(event)
3027 
3028     def newFloorPlanSelBotRightMode(self, event):
3029         self.clicked(event)
3030         self.floorPlanTopLeft = self.clickedTile
3031         self.floorPlanTopLeft.drawShape(HIGHLIGHT)
3032         self.view.setMouseTracking(True)  # Start mouse tracking
3033         self.updateNewFloorPlanVisualsFlag = True  # Start displaying visuals
3034         self.statusBar.showMessage('Select bottom right corner to create floor plan')
3035         self.scene.mouseReleaseEvent = lambda event: self.newFloorPlanSaveMode(event)
3036 
3037     def newFloorPlanSaveMode(self, event):
3038         self.clicked(event)
3039         self.floorPlanBottomRight = self.clickedTile
3040         self.updateNewFloorPlanVisualsFlag = False  # Stop displaying visuals
3041         self.view.setMouseTracking(False)  # Stop mouse tracking
3042 
3043         selectedTools = []
3044         selectedTools.clear()
3045         for tool in self.Machines:
3046             if self.floorPlanTopLeft.x <= tool.x <= self.floorPlanBottomRight.x \
3047                     and self.floorPlanBottomRight.y <= tool.y <= self.floorPlanTopLeft.y:
3048                 selectedTools.append(tool)
3049 
3050         self.floorPlans[self.selFloorPlan]['description'] = 'Saved Floor Plan %i' % (self.selFloorPlan + 1)
3051 
3052         self.floorPlans[self.selFloorPlan]['size'] = (
3053             int((self.floorPlanBottomRight.x - self.floorPlanTopLeft.x) / 25) + 1,
3054             int((self.floorPlanTopLeft.y - self.floorPlanBottomRight.y) / 25) + 1)
3055 
3056         # Loop thru machines in selected area to store location and properties
3057         xBottomLeftCorner = self.floorPlanTopLeft.x
3058         yBottomLeftCorner = self.floorPlanBottomRight.y
3059         self.floorPlans[self.selFloorPlan]['machines'].clear()
3060         for i, tool in enumerate(selectedTools):
3061             xRelative = tool.x - xBottomLeftCorner
3062             yRelative = tool.y - yBottomLeftCorner
3063             self.floorPlans[self.selFloorPlan]['machines'][i] = [tool.type, xRelative, yRelative, tool.orientation,
3064                                                                  tool.selectedBlueprint, tool.starterQuantity,
3065                                                                  tool.filterLeft, tool.filterRight, tool.teleporterID]
3066         self.floorPlanMenuFrame.reset()
3067         self.updateMessage('Floor Plan Saved!')
3068         self.closeMode()
3069         self.raiseFrame(self.floorPlanMenuFrame)
3070 
3071     def updateNewFloorPlanVisuals(self, event):
3072         point = event.scenePos()
3073         xCursor = point.x()
3074         yCursor = self.sceneHeight - point.y()
3075 
3076         x, y = self.getTileCenter(xCursor, yCursor)  # Check if cursor changed tiles or don't waste time updating
3077         if (x, y) != (self.xCursorTileCenter, self.yCursorTileCenter):
3078             self.xCursorTileCenter, self.yCursorTileCenter = x, y
3079 
3080             for tile in self.Tiles:
3081                 if self.floorPlanTopLeft.x <= tile.x <= self.xCursorTileCenter \
3082                         and self.yCursorTileCenter <= tile.y <= self.floorPlanTopLeft.y:
3083                     tile.shape_highlight.setVisible(True)
3084                 else:
3085                     tile.shape_highlight.setVisible(False)
3086 
3087     def placeFloorPlanSelTopLeftMode(self, planNum):
3088         self.closeMenus()
3089         self.deselectAllButtons()
3090         self.floorPlan_button.setStyleCode('Blue-Square-Menu-Bottom-Side')
3091         self.selFloorPlan = planNum
3092         for tile in self.Tiles:  # Pre-draw tile highlights & setVisibility
3093             tile.drawShape(HIGHLIGHT)
3094             tile.shape_highlight.setZValue(Z_HIGHLIGHT)
3095             tile.shape_highlight.setVisible(False)
3096         self.view.setMouseTracking(True)  # Start mouse tracking
3097         self.updatePlaceFloorPlanVisualsFlag = True  # Flag starts visuals
3098         self.statusBar.showMessage('Mode: Select valid location to place floor plan')
3099         self.scene.mouseReleaseEvent = lambda event: self.placeSelectedFloorPlan(event)
3100 
3101     def placeSelectedFloorPlan(self, event):
3102         self.clicked(event)
3103         self.floorPlanBottomRight = self.clickedTile
3104         if self.checkValidFloorPlanSpacing(self.clickedTile) is True:
3105             for key, value in self.floorPlans[self.selFloorPlan]['machines'].items():
3106                 self.buildMachineIfAbleToBuildAny(value[0],
3107                                                   value[1] + self.clickedTile.x,  # Conv self.floorPlan rel x, y to abs
3108                                                   value[2] + self.clickedTile.y,
3109                                                   *value[3:])
3110             self.updateMessage('Floor Plan Placed!')
3111         else:
3112             self.updateMessage('Invalid Area Selected')
3113 
3114     def checkValidFloorPlanSpacing(self, spot):
3115         i, j = self.floorPlans[self.selFloorPlan]['size']  # i, j = number of tiles in self.floorPlan
3116 
3117         # Check if floor plan will fit within scene area
3118         if spot.x + i * 25 > 1250 and spot.y + j * 25 > 400:
3119             print('plan wont fit on board')
3120             return False
3121 
3122         for tile in self.Tiles:
3123             if (spot.x) <= tile.x <= (spot.x + (i - 1) * 25) and (spot.y) <= tile.y <= (spot.y + (j - 1) * 25):
3124 
3125                 # Check if tile is locked or walled
3126                 if tile.walled is True or tile.locked is True:
3127                     return False  # Tile is locked or walled
3128 
3129                 # Check if tile contains a machine
3130                 for tool in self.Machines:
3131                     if (tool.x, tool.y) == (tile.x, tile.y):
3132                         return False  # Tile is occupied
3133         return True
3134 
3135     def updatePlaceFloorPlanVisuals(self, event):
3136         point = event.scenePos()
3137         xCursor = point.x()
3138         yCursor = self.sceneHeight - point.y()
3139 
3140         i, j = self.floorPlans[self.selFloorPlan]['size']
3141 
3142         x, y = self.getTileCenter(xCursor, yCursor)  # Check if cursor changed tiles or don't re-update
3143         if (x, y) != (self.xCursorTileCenter, self.yCursorTileCenter):
3144             self.xCursorTileCenter, self.yCursorTileCenter = x, y
3145 
3146             for tile in self.Tiles:
3147                 if (self.xCursorTileCenter) <= tile.x <= (self.xCursorTileCenter + (i - 1) * 25) \
3148                         and (self.yCursorTileCenter) <= tile.y <= (self.yCursorTileCenter + (j - 1) * 25):
3149                     tile.shape_highlight.setVisible(True)
3150                 else:
3151                     tile.shape_highlight.setVisible(False)
3152 
3153     def deselectAllButtons(self):
3154         self.build_button.setStyleCode('White-Square-Menu-Left-Side')
3155         self.move_button.setStyleCode('White-Square-Menu-Left-Side')
3156         self.rotate_button.setStyleCode('White-Square-Menu-Left-Side')
3157         self.tiles_button.setStyleCode('White-Square-Menu-Left-Side')
3158         self.sell_button.setStyleCode('White-Square-Menu-Left-Side')
3159         self.floorPlan_button.setStyleCode('White-Square-Menu-Bottom-Side')
3160         self.cancel_button.setStyleCode('White-Square-Menu-Right-Side')
3161         self.delAllArrows()
3162 
3163     def finishSetup(self):
3164         self.initializeValues()
3165         self.generateTileList()
3166         self.resetUnlockedParameterLists()
3167         self.markTilesLockedOrUnlocked()
3168         self.markAllTilesWalledOrNot()
3169         self.resetAllMenus()
3170         self.precomputeRoboticArmKinematics()
3171         self.startCoreLoopTimer()
3172 
3173     def startCoreLoopTimer(self):
3174         self.timer = QtCore.QTimer()
3175         self.timer.timeout.connect(self.coreLoop.run)
3176         self.timer.start(CYCLE_INTERVAL)
3177 
3178     def saveConfig(self):
3179         self.db.clear()
3180         self.db['machines'] = {}
3181         for i, tool in enumerate(self.Machines):
3182             self.db['machines'][i] = [tool.type, tool.x, tool.y, tool.orientation,
3183                                       tool.selectedBlueprint, tool.starterQuantity,
3184                                       tool.filterLeft, tool.filterRight, tool.teleporterID]
3185         self.db['balance'] = self.balance
3186         self.db['unlockedMachines'] = self.unlockedMachines
3187         self.db['unlockedBlueprints'] = self.unlockedBlueprints
3188         self.db['unlockedResearch'] = self.unlockedResearch
3189         self.db['unlockedAssyLines'] = self.unlockedAssyLines
3190         self.db['unlockedAchievements'] = self.unlockedAchievements
3191         self.db['unlockedTiles'] = self.unlockedTiles
3192         self.db['starterMaxSpawnQuantity'] = self.starterMaxSpawnQuantity
3193         self.db['maxStarters'] = self.maxStarters
3194         self.db['maxTeleporters'] = self.maxTeleporters
3195         self.db['opCostModifier'] = self.opCostModifier
3196         self.db['opTimeModifierStarterCrafter'] = self.opTimeModifierStarterCrafter
3197         self.db['opTimeModifierTier2Machines'] = self.opTimeModifierTier2Machines
3198         self.db['floorPlans'] = self.floorPlans
3199 
3200         self.dbfile = open('saveFile', 'wb')  # w = overwrite, a = append
3201         pickle.dump(self.db, self.dbfile)
3202         self.dbfile.close()
3203         self.updateMessage('Game Saved!')
3204         self.statusBar.showMessage('Game Saved!')
3205 
3206     def loadConfig(self):
3207         self.reset()
3208 
3209         self.db.clear()
3210         self.db['machines'] = {}
3211         self.dbfile = open('saveFile', 'rb')
3212         self.db = pickle.load(self.dbfile)
3213         self.dbfile.close()
3214 
3215         for key, value in self.db['machines'].items():
3216             self.Machines.append(clsMachine(self, *value))
3217         self.updateBalance(self.db['balance'])
3218         self.unlockedMachines = self.db['unlockedMachines']
3219         self.unlockedBlueprints = self.db['unlockedBlueprints']
3220         self.unlockedResearch = self.db['unlockedResearch']
3221         self.unlockedAssyLines = self.db['unlockedAssyLines']
3222         self.unlockedAchievements = self.db['unlockedAchievements']
3223         self.unlockedTiles = self.db['unlockedTiles']
3224         self.starterMaxSpawnQuantity = self.db['starterMaxSpawnQuantity']
3225         self.maxStarters = self.db['maxStarters'] = self.maxStarters
3226         self.maxTeleporters = self.db['maxTeleporters']
3227         self.opCostModifier = self.db['opCostModifier'] = self.opCostModifier
3228         self.opTimeModifierStarterCrafter = self.db['opTimeModifierStarterCrafter']
3229         self.opTimeModifierTier2Machines = self.db['opTimeModifierTier2Machines']
3230         self.floorPlans = self.db['floorPlans']
3231 
3232         self.markTilesLockedOrUnlocked()  # Load changes due to self.unlockedTiles
3233         self.markAllTilesWalledOrNot()  # Load changes due to self.unlockedAssyLines
3234         self.resetAllMenus()  # Load changes due to changed parameters
3235         self.metric_label.setText(
3236             '%i / %i Achievements Unlocked' % (len(self.unlockedAchievements), self.getAmountOfAchievements()))
3237         self.updateMessage('Game Loaded!')
3238         self.statusBar.showMessage('Game Loaded!')
3239 
3240         # Debugging
3241         # print(self.unlockedBlueprints)
3242 
3243         # Debugging - Remove all selected & considered blueprints from Starters & Crafters
3244         # for machine in self.Machines:
3245         #     if machine.type in [STARTER, CRAFTER]:
3246         #         machine.consideredBlueprints = []
3247         #         machine.selectedBlueprint = None
3248 
3249         # Debugging - Unlock blueprint manually
3250         # self.unlockedBlueprints.append('Speaker')
3251 
3252     def reset(self):
3253         self.deleteAllMachinesAndMaterials()
3254         self.salesCollector.clear()  # Delete everything in achievements sales collector
3255         self.resetUnlockedParameterLists()
3256         self.markTilesLockedOrUnlocked()
3257         self.markAllTilesWalledOrNot()
3258         self.removeAchievementNotification()  # Remove any open notification
3259         self.initializeValues()  # Set variables to initial values
3260         self.resetAllMenus()
3261         self.closeMenus()
3262         self.closeMode()
3263         self.queueReset = False
3264         self.metric_label.setText(
3265             '%i / %i Achievements Unlocked' % (len(self.unlockedAchievements), self.getAmountOfAchievements()))
3266         self.updateMessage('Game Reset!')
3267         self.statusBar.showMessage('Game Reset!')
3268 
3269     def resetUnlockedParameterLists(self):
3270         self.unlockedMachines = [STARTER, SELLER]
3271         self.unlockedBlueprints = [
3272             'Copper', 'Gold', 'Iron', 'Aluminum', 'Crystal',
3273             'Copper Wire', 'Gold Wire', 'Iron Wire', 'Aluminum Wire', 'Crystal Wire',
3274             'Copper Gear', 'Gold Gear', 'Iron Gear', 'Aluminum Gear', 'Crystal Gear',
3275             'Molten Copper', 'Molten Gold', 'Molten Iron', 'Molten Aluminum', 'Molten Crystal',
3276             'Copper Plate', 'Gold Plate', 'Iron Plate', 'Aluminum Plate', 'Crystal Plate',
3277             'Circuit'
3278         ]
3279         self.unlockedResearch.clear()
3280         self.unlockedAssyLines.clear()
3281         self.unlockedAchievements.clear()
3282         self.unlockedTiles.clear()
3283         # Index of tiles to mark as always unlocked
3284         for i in list(range(0, 96)) + list(range(272, 368)) + list(range(544, 640)):
3285             self.unlockedTiles.append((self.Tiles[i].x, self.Tiles[i].y))
3286 
3287     def resetAllMenus(self):
3288         self.buildMenuFrame.reset()
3289         self.blueprintsMenuFrame.reset()
3290         self.researchMenuFrame.reset()
3291         self.assyLineMenuFrame.reset()
3292         self.achievementsMenuFrame.reset()
3293         self.incomeAnalysisMenuFrame.reset()
3294         self.floorPlanMenuFrame.reset()
3295 
3296     def deleteAllMachinesAndMaterials(self):
3297         while len(self.Machines) > 0:  # Deleting items in list modifies index
3298             self.Machines[0].delMachine()
3299 
3300         while len(self.Materials) > 0:  # Deleting items in list modifies index
3301             self.Materials[0].delMaterial()
3302 
3303     def initializeValues(self):
3304         self.balance = 15000  # Proper initial balance for new game is 15,000
3305         # self.balance = 500000000000  # Balance for debugging
3306 
3307         self.balance_label.setText('Balance: $%s' % '{:,}'.format(self.balance))
3308         self.moneyRate_label.setText('Profit: $%s / Second' % '{:,}'.format(self.moneyRate))
3309         self.itemRate_label.setText('Sales: %s Items / Second' % '{:,}'.format(self.itemRate))
3310         self.statusBar.showMessage('None')
3311         self.message_label.setText('New Game!')
3312 
3313         self.opCostModifier = 1  # OpCost = 5,3,1 so mod starts 1 and goes down by 0.4 twice
3314         self.maxStarters = 10  # Max per line
3315         self.maxTeleporters = 10  # Max per all lines combined
3316         self.opTimeModifierStarterCrafter = 0  # Is 0 and inc 1 2x, op_time is 3s and dec 1s 2x
3317         self.opTimeModifierTier2Machines = 0  # Is 0 and inc 1 2x, op_time is 3s and dec 1s 2x
3318         self.starterMaxSpawnQuantity = 1  # Starts at 1 max and goes up by 1 twice
3319 
3320     def setQueueReset(self):
3321         self.queueReset = True
3322 
3323     def generateTileList(self):  # Create a list with all tiles objects
3324         self.Tiles.clear()
3325         for i in range(13, self.sceneWidth, 25):
3326             for j in range(13, self.sceneHeight, 25):
3327                 self.Tiles.append(clsTile(self, i, j))
3328 
3329     def markTilesLockedOrUnlocked(self):
3330         for tile in self.Tiles:
3331             if (tile.x, tile.y) in self.unlockedTiles:
3332                 tile.markAsUnlocked()
3333             else:
3334                 tile.markAsLocked()
3335 
3336     def buyAssyLine(self, lineNumber, price):
3337         if self.balance >= price:
3338             if lineNumber == 'Line2':
3339                 self.unlockedAssyLines.append(lineNumber)
3340                 self.markAllTilesWalledOrNot()
3341                 self.assyLineMenuFrame.reset()
3342 
3343             elif lineNumber == 'Line3':
3344                 self.unlockedAssyLines.append(lineNumber)
3345                 self.markAllTilesWalledOrNot()
3346                 self.assyLineMenuFrame.reset()
3347 
3348             self.updateBalance(self.balance - price)
3349             self.updateMessage('Bought new Assy Line line for $%s!' % self.shortNum(price))
3350         else:
3351             self.updateMessage('Not enough money')
3352 
3353     def markAllTilesWalledOrNot(self):
3354         if 'Line2' in self.unlockedAssyLines:
3355             [self.Tiles[i].markAsUnwalled() for i in range(272, 528)]
3356         else:
3357             [self.Tiles[i].markAsWalled() for i in range(272, 528)]
3358         if 'Line3' in self.unlockedAssyLines:
3359             [self.Tiles[i].markAsUnwalled() for i in range(544, 800)]
3360         else:
3361             [self.Tiles[i].markAsWalled() for i in range(544, 800)]
3362         [self.Tiles[i].markAsWalled() for i in list(range(256, 272)) + list(range(528, 544))]  # Border walls
3363 
3364     def getNumberMachinesInClickedAssyLine(self, machineType):
3365         machineCount = 0
3366         for tool in self.Machines:
3367             if tool.type == machineType and tool.assyLine == self.clickedTile.assyLine:
3368                 machineCount += 1
3369         return machineCount
3370 
3371     def updateBalance(self, newBalance):
3372         self.balance = int(newBalance)
3373         self.balance_label.setText('Balance: $%s' % '{:,}'.format(self.balance))
3374 
3375     def updateMessage(self, message):
3376         self.message_label.setText(message)
3377         self.messageTimer = self.iteration
3378 
3379     def updateEvent(self, event):
3380         self.event_label.setText(event)
3381         self.eventTimer = self.iteration
3382 
3383     def checkAchievements(self):
3384         # Check for material sales achievements
3385         for key, value in self.salesCollector.items():
3386             if key not in self.unlockedAchievements:
3387                 self.unlockedAchievements.append(key)
3388                 self.achievementsMenuFrame.reset()
3389                 self.achievementNotification('Sold %s' % key)
3390 
3391         # Check for profit per second achievements
3392         if 'Profit I' not in self.unlockedAchievements and self.moneyRate >= 1000000:
3393             self.unlockedAchievements.append('Profit I')
3394             self.achievementsMenuFrame.reset()
3395             self.achievementNotification('Profit I')
3396         if 'Profit II' not in self.unlockedAchievements and self.moneyRate >= 10000000:
3397             self.unlockedAchievements.append('Profit II')
3398             self.achievementsMenuFrame.reset()
3399             self.achievementNotification('Profit II')
3400         if 'Profit III' not in self.unlockedAchievements and self.moneyRate >= 100000000:
3401             self.unlockedAchievements.append('Profit III')
3402             self.achievementsMenuFrame.reset()
3403             self.achievementNotification('Profit III')
3404 
3405         # Check for sales per second achievements
3406         if 'Scale I' not in self.unlockedAchievements and self.itemRate >= 1:
3407             self.unlockedAchievements.append('Scale I')
3408             self.achievementsMenuFrame.reset()
3409             self.achievementNotification('Scale I')
3410         if 'Scale II' not in self.unlockedAchievements and self.itemRate >= 5:
3411             self.unlockedAchievements.append('Scale II')
3412             self.achievementsMenuFrame.reset()
3413             self.achievementNotification('Scale II')
3414         if 'Scale III' not in self.unlockedAchievements and self.itemRate >= 10:
3415             self.unlockedAchievements.append('Scale III')
3416             self.achievementsMenuFrame.reset()
3417             self.achievementNotification('Scale III')
3418 
3419         # Check for all possible items sold achievement
3420         if 'Sell Every Items' not in self.unlockedAchievements \
3421                 and all(k in self.unlockedAchievements for k in list(self.materialLib.lib.keys())):
3422             self.unlockedAchievements.append('Sell Every Items')
3423             self.achievementsMenuFrame.reset()
3424             self.achievementNotification('Sell Every Items')
3425 
3426         # Check for all possible assembly lines unlocked achievement
3427         if 'Max Assembly Lines' not in self.unlockedAchievements \
3428                 and all(k in self.unlockedAssyLines for k in ['Line2', 'Line3']):
3429             self.unlockedAchievements.append('Max Assembly Lines')
3430             self.achievementsMenuFrame.reset()
3431             self.achievementNotification('Max Assembly Lines')
3432 
3433         # Check for all possible research options unlocked achievement
3434         if 'Unlock All Research' not in self.unlockedAchievements \
3435                 and all(k in self.unlockedAchievements for k in list(self.researchLib.lib.keys())):
3436             self.unlockedAchievements.append('Unlock All Research')
3437             self.achievementsMenuFrame.reset()
3438             self.achievementNotification('Unlock All Research')
3439 
3440     def achievementNotification(self, title):
3441         self.achievementTimer = self.iteration
3442         self.achievementPopUpMenuFrame.achievementName.setText('Achievement Unlocked!\n%s' % title)
3443         self.metric_label.setText(
3444             '%i / %i Achievements Unlocked' % (len(self.unlockedAchievements), self.getAmountOfAchievements()))
3445         self.achievementPopUpMenuFrame.raise_()
3446 
3447     def removeAchievementNotification(self):
3448         self.lowerFrame(self.achievementPopUpMenuFrame)
3449 
3450     def getAmountOfAchievements(self):
3451         return len(self.achievementLib.lib) - 1 + len(self.materialLib.lib)  # Sell each item plus others
3452 
3453     def fadeMessage(self):
3454         if not self.message_label.text().startswith(' '):
3455             self.updateMessage('  ' + str(self.message_label.text()))
3456 
3457     def fadeEvent(self):
3458         if not self.event_label.text().startswith(' '):
3459             self.updateEvent('  ' + str(self.event_label.text()))
3460 
3461     def activateValidTeleporters(self):  # Prevents multiple inputs going to same output
3462         self.teleporterInputIDs.clear()  # [ID], [Objects with that matching ID]
3463         self.teleporterOutputIDs.clear()  # [ID], [Objects with that matching ID]
3464 
3465         for tool in self.Machines:
3466             if tool.type == TELEPORTER_INPUT:
3467                 # Return value for key if exists otherwise return empty list, can't use append because list is destroyed
3468                 self.teleporterInputIDs[tool.teleporterID] = self.teleporterInputIDs.setdefault(tool.teleporterID,
3469                                                                                                 []) + [tool]
3470             if tool.type == TELEPORTER_OUTPUT:
3471                 # Return value for key if exists otherwise return empty list, can't use append because list is destroyed
3472                 self.teleporterOutputIDs[tool.teleporterID] = self.teleporterOutputIDs.setdefault(tool.teleporterID,
3473                                                                                                   []) + [tool]
3474 
3475         for key, value in self.teleporterInputIDs.items():
3476             if len(value) > 1:
3477                 for tool in value:
3478                     tool.teleporterActivated = False
3479             else:
3480                 for tool in value:
3481                     tool.teleporterActivated = True
3482 
3483         for key, value in self.teleporterOutputIDs.items():
3484             if len(value) > 1:
3485                 for tool in value:
3486                     tool.teleporterActivated = False
3487             else:
3488                 for tool in value:
3489                     tool.teleporterActivated = True
3490 
3491     def moneyRateAnalyze(self):
3492         self.lastMRAnalysisTime = datetime.datetime.now()
3493         self.salesAnalysis = self.salesCollector.copy()  # Make a copy of salesCollector
3494         self.salesCollector.clear()
3495 
3496         totalIncome = 0  # Reset totalIncome to 0
3497         totalSales = 0  # Reset sales to 0
3498         if self.salesAnalysis:  # Check that dictionary is not empty
3499             for i, (key, value) in enumerate(self.salesAnalysis.items()):
3500                 if i < 9:
3501                     self.incomeAnalysisMenuFrame.wids[i]['Type'].setText(key)
3502                     self.incomeAnalysisMenuFrame.wids[i]['Count'].setText(str(value))
3503                     self.incomeAnalysisMenuFrame.wids[i]['Profit'].setText(
3504                         '$' + str(self.shortNum(self.materialLib.lib[key]['value'] * value)))
3505                     self.incomeAnalysisMenuFrame.wids[i]['PPS'].setText(
3506                         '$' + str(self.shortNum(self.materialLib.lib[key]['value'] * value / INCOME_ANALYSIS_FREQ)))
3507                 totalIncome += self.materialLib.lib[key]['value'] * value / INCOME_ANALYSIS_FREQ
3508                 totalSales += value / INCOME_ANALYSIS_FREQ
3509         self.moneyRate_label.setText('Profit: $%s / Second' % self.shortNum(totalIncome))
3510         self.itemRate_label.setText('Sales: %s Items / Second' % str(round(totalSales, 2)))
3511 
3512     def frameRateAnalyze(self):
3513         self.lastFrameRateAnalysisTime = datetime.datetime.now()
3514 
3515         if len(self.frameRateResultSet) > 0:  # Prevent running if frameRateResultSet is empty
3516 
3517             frames = 0
3518             setpoint = CYCLE_INTERVAL
3519             target = int(CYCLE_INTERVAL * 1.5)
3520             high = 0
3521             highest = 0
3522 
3523             for item in self.frameRateResultSet:
3524                 frames += 1
3525                 if item > target:
3526                     high += 1
3527                 if item > highest:
3528                     highest = item
3529 
3530             averageFPS = int(1000 / statistics.mean(self.frameRateResultSet))
3531 
3532             self.frameRateMenuFrame.wids['Frames'].setText(str(frames))
3533             self.frameRateMenuFrame.wids['Setpoint'].setText(str(setpoint))
3534             self.frameRateMenuFrame.wids['High Limit'].setText(str(target))
3535             self.frameRateMenuFrame.wids['High'].setText(str(high) + '%')
3536             self.frameRateMenuFrame.wids['Highest'].setText(str(highest))
3537             self.frameRateMenuFrame.wids['AverageFPS'].setText(str(averageFPS))
3538 
3539             self.frameRateMenuFrame.plot()
3540 
3541             self.frameRate_button.setText('FPS (%s)' % str(averageFPS))
3542 
3543         self.frameRateResultSet.clear()
3544 
3545     def unlockMachine(self, machine):
3546         if self.balance >= self.machineLib.lib[machine]['unlock']:
3547             self.updateBalance(self.balance - self.machineLib.lib[machine]['unlock'])
3548             self.unlockedMachines.append(machine)
3549             self.buildMenuFrame.wids[machine]['lockImg'].lower()
3550             self.buildMenuFrame.wids[machine]['lockLabel'].lower()
3551             self.updateMessage('Unlocked machine type: %s' % machine)
3552         else:
3553             self.updateMessage('Not enough money')
3554 
3555     def unlockBlueprint(self, material):
3556         if self.balance >= self.materialLib.lib[material]['unlock']:
3557             self.blueprintsMenuFrame.wids[material]['base']['cover'].lower()
3558             self.blueprintsMenuFrame.wids[material]['base']['lock'].lower()
3559             self.updateBalance(self.balance - self.materialLib.lib[material]['unlock'])
3560             self.unlockedBlueprints.append(material)
3561             self.updateMessage('Unlocked Blueprint: %s' % material)
3562         else:
3563             self.updateMessage('Not enough money')
3564 
3565     def unlockResearch(self, option):
3566         if self.balance >= self.researchLib.lib[option]['cost']:
3567             self.unlockedResearch.append(option)
3568             self.updateBalance(self.balance - self.researchLib.lib[option]['cost'])
3569             self.researchMenuFrame.reset()
3570             self.updateMessage('Purchased Research for $%s' % self.shortNum(self.researchLib.lib[option]['cost']))
3571             if self.researchLib.lib[option]['type'] == 'opCostModifier':
3572                 self.opCostModifier = round(self.opCostModifier - 0.4, 1)  # Prevents any slightly off decimals
3573             if self.researchLib.lib[option]['type'] == 'maxStarters':
3574                 self.maxStarters = self.maxStarters + self.researchLib.lib[option]['amount']
3575             if self.researchLib.lib[option]['type'] == 'maxTeleporters':
3576                 self.maxTeleporters = self.maxTeleporters + self.researchLib.lib[option]['amount']
3577             if self.researchLib.lib[option]['type'] == 'opTimeStarterCrafter':
3578                 self.opTimeModifierStarterCrafter = self.opTimeModifierStarterCrafter + 1
3579             if self.researchLib.lib[option]['type'] == 'opTimeTier2Machines':
3580                 self.opTimeModifierTier2Machines = self.opTimeModifierTier2Machines + 1
3581             if self.researchLib.lib[option]['type'] == 'starterMaxSpawnQuantity':
3582                 self.starterMaxSpawnQuantity = self.starterMaxSpawnQuantity + self.researchLib.lib[option]['amount']
3583             if self.researchLib.lib[option]['type'] == 'floorPlanFeature':
3584                 self.floorPlanMenuFrame.reset()
3585         else:
3586             self.updateMessage('Not enough money')
3587 
3588     def drawAllArrows(self):
3589         for tool in self.Machines:
3590             tool.drawArrow()
3591 
3592     def delAllArrows(self):
3593         for tool in self.Machines:
3594             tool.delArrow()
3595 
3596     def delAllHighlights(self):
3597         for tile in self.Tiles:
3598             tile.delShape(HIGHLIGHT)
3599 
3600     def getTile(self, x, y):
3601         for tile in self.Tiles:
3602             if (tile.x, tile.y) == (x, y):
3603                 return tile
3604         return None
3605 
3606     def getMachine(self, x, y):
3607         for tool in self.Machines:
3608             if (tool.x, tool.y) == (x, y):
3609                 return tool
3610         return None
3611 
3612     # -------- Generic Methods -------- #
3613 
3614     def convertToSceneCoords(self, xApp, yApp, imgW, imgH):
3615         xScene = xApp - int(imgW / 2)  # Account for top left image origin, not center
3616         yScene = yApp + int(imgH / 2) - 1  # Lower by 1px for better visual
3617         xScene = xScene  # Account for top left scene origin, not bottom left
3618         yScene = self.sceneHeight - yScene
3619         return xScene, yScene
3620 
3621     @staticmethod
3622     def shortNum(n):
3623         if n < 1000:
3624             return str(round(n))
3625         if 1000 <= n < 1000000:
3626             return str(round(n / 1000, 1)).rstrip('0').rstrip('.') + ' K'
3627         if 1000000 <= n < 1000000000:
3628             return str(round(n / 1000000, 1)).rstrip('0').rstrip('.') + ' M'
3629         if n >= 1000000000:
3630             return str(round(n / 1000000000, 1)).rstrip('0').rstrip('.') + ' B'
3631 
3632     @staticmethod
3633     def getTileCenter(x, y):
3634         xTileCenter = int(((x // 25) * 25) + 13)
3635         yTileCenter = int(((y // 25) * 25) + 13)
3636         return xTileCenter, yTileCenter
3637 
3638     @staticmethod
3639     def getSystemInfo():
3640         # print('Width =', GetSystemMetrics(0), 'Height =', GetSystemMetrics(1))    # Single monitor
3641         # print('Width =', GetSystemMetrics(78), 'Height =', GetSystemMetrics(79))  # Combined multi-monitor
3642         return GetSystemMetrics(0), GetSystemMetrics(1), GetSystemMetrics(78), GetSystemMetrics(79)
3643 
3644     # -------- Robotic Arm Kinematics -------- #
3645 
3646     def precomputeRoboticArmKinematics(self):
3647         # Compute positions of ends of linkages, B and C relative to the tile center over each phase of motion.
3648         # Compute angles of each linage over each phase of motion.
3649         # Translate position of B and C to centerpoint of linkage AB and BC by taking average of tile center and B etc...
3650         # This set assumes machine is defaulting to pointing downward and 0 degree angle is to the right and image starts at 0 deg
3651         # Create secondary sets of positions for tool orientations, U, L, R by taking the positive or negative x and y distances
3652         # from the D orientation set as appropriate and adding 0, 90, 180, or 270 deg to the linkage angles.
3653         # End result is a set of relative positions for AB, BC and angles for thetaAB and thetaBC for each tool orientation for
3654         # each frame number.
3655         #
3656         # Definitions:
3657         # xRelB, yRelB = Relative position of B
3658         # xRelC, yRelC = Relative position of C
3659         # xRelLinkCenterAB, yRelLinkCenterAB = Relative of position of center of link AB
3660         # xRelLinkCenterBC, yRelLinkCenterBC = Relative of position of center of link BC
3661         # thetaAB = Angle of link AB
3662         # thetaBC = Angle of link BC
3663         # Each term has an associated tool orientation and frame number, ['D'][1], for example.
3664         #
3665         # Geometry:
3666         # Two Linkage Arm = A [---AB---] B [---BC---] C
3667 
3668         # Variables
3669         self.thetaAB = {'U': {}, 'L': {}, 'D': {}, 'R': {}}  # Angle of link AB
3670         self.thetaBC = {'U': {}, 'L': {}, 'D': {}, 'R': {}}  # Angle of link BC
3671         self.xRelB = {'U': {}, 'L': {}, 'D': {}, 'R': {}}  # Relative x position of point B (Relative to tool center)
3672         self.yRelB = {'U': {}, 'L': {}, 'D': {}, 'R': {}}  # Relative y position of point B
3673         self.xRelC = {'U': {}, 'L': {}, 'D': {}, 'R': {}}  # Relative x position of point C
3674         self.yRelC = {'U': {}, 'L': {}, 'D': {}, 'R': {}}  # Relative y position of point C
3675 
3676         # Create table of link end points. Assumes tool faces is downward by convention
3677         for i in range(1, 13):
3678             self.thetaAB['D'][i] = 270 + 5 * i  # Input: Start at 270 deg and inc 60deg over 12 steps
3679             self.thetaBC['D'][i] = 270 - 5 * i  # Input: Start at 270 deg and dec 60deg over 12 steps
3680             self.xRelB['D'][i] = self.xArc(12, self.thetaAB['D'][i])
3681             self.yRelB['D'][i] = self.yArc(12, self.thetaAB['D'][i])
3682             self.xRelC['D'][i] = self.xRelB['D'][i] + self.xArc(12, self.thetaBC['D'][i])
3683             self.yRelC['D'][i] = self.yRelB['D'][i] + self.yArc(12, self.thetaBC['D'][i])
3684 
3685         for i in range(13, 37):
3686             self.thetaAB['D'][i] = 330 + (180 / 24) * (i - 12)  # Input: Start at 330 deg and dec 180deg over 24 steps
3687             self.thetaBC['D'][i] = 210 + (180 / 24) * (i - 12)  # Input: Start at 210 deg and inc 180deg over 24 steps
3688             self.xRelB['D'][i] = self.xArc(12, self.thetaAB['D'][i])
3689             self.yRelB['D'][i] = self.yArc(12, self.thetaAB['D'][i])
3690             self.xRelC['D'][i] = self.xRelB['D'][i] + self.xArc(12, self.thetaBC['D'][i])
3691             self.yRelC['D'][i] = self.yRelB['D'][i] + self.yArc(12, self.thetaBC['D'][i])
3692 
3693         for i in range(37, 49):
3694             self.thetaAB['D'][i] = 150 - (60 / 12) * (i - 36)  # Input: Start at 150deg and dec 60deg over 12 steps
3695             self.thetaBC['D'][i] = 30 + (60 / 12) * (i - 36)  # Input: Start at 30 deg and inc 60deg over 12 steps
3696             self.xRelB['D'][i] = self.xArc(12, self.thetaAB['D'][i])
3697             self.yRelB['D'][i] = self.yArc(12, self.thetaAB['D'][i])
3698             self.xRelC['D'][i] = self.xRelB['D'][i] + self.xArc(12, self.thetaBC['D'][i])
3699             self.yRelC['D'][i] = self.yRelB['D'][i] + self.yArc(12, self.thetaBC['D'][i])
3700 
3701         # Create tables for remaining tool orientations by multiplying by 1, -1, and/or swapping x and y
3702         for i in range(1, 49):
3703             self.xRelB['U'][i], self.yRelB['U'][i], self.thetaAB['U'][i] = \
3704                 (-self.xRelB['D'][i], -self.yRelB['D'][i], int(self.thetaAB['D'][i] + 180) % 360)
3705             self.xRelB['L'][i], self.yRelB['L'][i], self.thetaAB['L'][i] = \
3706                 (+self.yRelB['D'][i], -self.xRelB['D'][i], int(self.thetaAB['D'][i] + 270) % 360)
3707             self.xRelB['D'][i], self.yRelB['D'][i], self.thetaAB['D'][i] = \
3708                 (+self.xRelB['D'][i], +self.yRelB['D'][i], int(self.thetaAB['D'][i] + 0) % 360)
3709             self.xRelB['R'][i], self.yRelB['R'][i], self.thetaAB['R'][i] = \
3710                 (-self.yRelB['D'][i], +self.xRelB['D'][i], int(self.thetaAB['D'][i] + 90) % 360)
3711 
3712             self.xRelC['U'][i], self.yRelC['U'][i], self.thetaBC['U'][i] = \
3713                 (-self.xRelC['D'][i], -self.yRelC['D'][i], int(self.thetaBC['D'][i] + 180) % 360)
3714             self.xRelC['L'][i], self.yRelC['L'][i], self.thetaBC['L'][i] = \
3715                 (+self.yRelC['D'][i], -self.xRelC['D'][i], int(self.thetaBC['D'][i] + 270) % 360)
3716             self.xRelC['D'][i], self.yRelC['D'][i], self.thetaBC['D'][i] = \
3717                 (+self.xRelC['D'][i], +self.yRelC['D'][i], int(self.thetaBC['D'][i] + 0) % 360)
3718             self.xRelC['R'][i], self.yRelC['R'][i], self.thetaBC['R'][i] = \
3719                 (-self.yRelC['D'][i], +self.xRelC['D'][i], int(self.thetaBC['D'][i] + 90) % 360)
3720 
3721         # Variables for link centers and angles
3722         self.xRelLinkCenterAB = {'U': {}, 'L': {}, 'D': {}, 'R': {}}  # Relative x position of center of link AB
3723         self.yRelLinkCenterAB = {'U': {}, 'L': {}, 'D': {}, 'R': {}}  # Relative y position of center of link AB
3724         self.xRelLinkCenterBC = {'U': {}, 'L': {}, 'D': {}, 'R': {}}  # Relative x position of center of link BC
3725         self.yRelLinkCenterBC = {'U': {}, 'L': {}, 'D': {}, 'R': {}}  # Relative y position of center of link BC
3726 
3727         # Create table of relative link center positions based on relative point positions
3728         for tDir in ['U', 'L', 'D', 'R']:
3729             for i in range(1, 49):
3730                 self.xRelLinkCenterAB[tDir][i] = int(self.xRelB[tDir][i] / 2)
3731                 self.yRelLinkCenterAB[tDir][i] = int(self.yRelB[tDir][i] / 2)
3732                 self.xRelLinkCenterBC[tDir][i] = int(
3733                     self.xRelB[tDir][i] + (self.xRelC[tDir][i] - self.xRelB[tDir][i]) / 2)
3734                 self.yRelLinkCenterBC[tDir][i] = int(
3735                     self.yRelB[tDir][i] + (self.yRelC[tDir][i] - self.yRelB[tDir][i]) / 2)
3736 
3737         # Create set of Pixmaps at all angles. PyQt defaults to CW so negative makes it CCW to match kinematics convention
3738         self.angledPixmapLink1 = {}
3739         for i in range(0, 361):
3740             self.angledPixmapLink1[i] = QtGui.QPixmap('images/Robotic Arm Link 1.gif')
3741             transform = QtGui.QTransform().rotate(-i)
3742             self.angledPixmapLink1[i] = self.angledPixmapLink1[i].transformed(transform, QtCore.Qt.SmoothTransformation)
3743 
3744         self.angledPixmapLink2 = {}
3745         for i in range(0, 361):
3746             self.angledPixmapLink2[i] = QtGui.QPixmap('images/Robotic Arm Link 2.gif')
3747             transform = QtGui.QTransform().rotate(-i)
3748             self.angledPixmapLink2[i] = self.angledPixmapLink2[i].transformed(transform, QtCore.Qt.SmoothTransformation)
3749 
3750     @staticmethod
3751     def xArc(radius, angleDeg):
3752         angleRad = math.radians(angleDeg)
3753         return int(radius * math.cos(angleRad))
3754 
3755     @staticmethod
3756     def yArc(radius, angleDeg):
3757         angleRad = math.radians(angleDeg)
3758         return int(radius * math.sin(angleRad))
3759 
3760     def getAnyNearbyMaterial(self, piece):
3761         for material in self.oMaterials:
3762             if piece.x - 1 <= material.x <= piece.x + 1 and piece.y - 1 <= material.y <= piece.y + 1:
3763                 return material
3764         return None
3765 
3766     def assignMaterialToNewGroup(self, material):
3767         for i in range(0, 99):
3768             if i not in self.groupIDList or len(self.groupIDList[i]) == 0:
3769                 self.groupIDList[i] = [material]
3770                 material.groupID = i
3771                 material.groupPos = 0
3772                 material.setGroupVisualOffset(VISUAL_OFFSET_1_TO_2)
3773                 return
3774 
3775     def assignMaterialToGroup(self, material, group):
3776         self.groupIDList[group].append(material)
3777         material.groupID = group
3778         material.groupPos = self.getFreePosInGroup(group)
3779         if len(self.groupIDList[group]) <= 2:
3780             offsetList = VISUAL_OFFSET_1_TO_2  # Different offset configs based on group size
3781         elif len(self.groupIDList[group]) == 3:
3782             offsetList = VISUAL_OFFSET_3_TO_3
3783         elif len(self.groupIDList[group]) == 4:
3784             offsetList = VISUAL_OFFSET_4_TO_4
3785         else:
3786             offsetList = VISUAL_OFFSET_5_TO_9
3787         for item in self.groupIDList[group]:
3788             item.setGroupVisualOffset(offsetList)
3789 
3790     def getFreePosInGroup(self, group):
3791         for i in range(0, 99):
3792             posTakenFlag = False
3793             for material in self.groupIDList[group]:
3794                 if material.groupPos == i:
3795                     posTakenFlag = True
3796                     break
3797             if posTakenFlag is False:
3798                 return i
3799 
3800 
3801 class clsCoreLoop:
3802     def __init__(self, main):
3803         self.iteration = None
3804         self.main = main
3805 
3806     def run(self):
3807         # Iteration to Iteration Cycle Time
3808         iterationToIterationTime = int(
3809             (datetime.datetime.now() - self.main.iterationStartTime).total_seconds() * 1000)  # ms
3810 
3811         # Cycle time stabilization
3812         self.main.iterationStartTime = datetime.datetime.now()
3813 
3814         # Get working copies of Machines and Materials to prevent mid iteration changes to lists (Prevents bugs)
3815         self.main.oMachines = self.main.Machines.copy()
3816         self.main.oMaterials = self.main.Materials.copy()
3817 
3818         # Money rate analysis
3819         if (self.main.iterationStartTime - self.main.lastMRAnalysisTime).total_seconds() \
3820                 >= INCOME_ANALYSIS_FREQ:
3821             self.main.moneyRateAnalyze()
3822 
3823         # Frame rate analysis
3824         if (self.main.iterationStartTime - self.main.lastFrameRateAnalysisTime).total_seconds() \
3825                 >= FRAME_RATE_ANALYSIS_FREQ:
3826             self.main.frameRateAnalyze()
3827 
3828         # Check for achievements
3829         if self.main.iteration % 10 == 0:  # Achievements check freq
3830             self.main.checkAchievements()
3831 
3832         # Fade message & event each loop
3833         if self.main.iteration - self.main.messageTimer > 40:  # Fade after this many iterations
3834             self.main.fadeMessage()
3835         if self.main.iteration - self.main.eventTimer > 40:  # Fade after this many iterations
3836             self.main.fadeEvent()
3837 
3838         # Remove achievement notification after time limit
3839         if self.main.iteration - self.main.achievementTimer > 250:  # Fade after this many iterations
3840             self.main.removeAchievementNotification()
3841 
3842         # Check if any machines can launch or transform materials then execute
3843         if self.main.iteration % MAT_LAUNCH_INTERVAL == 0:  # Only run every x iterations
3844             for tool in self.main.oMachines:
3845 
3846                 # Decrement tool queue timer and create material if timer is up
3847                 if tool.queueDelay > 0:
3848                     tool.queueDelay -= 1
3849 
3850                 # Create material if timer is up
3851                 if tool.queueDelay == 0 and tool.queueMaterial is not None:
3852                     self.main.Materials.append(clsMaterial(self.main, tool.queueMaterial,
3853                                                            tool.x, tool.y, tool.orientation,
3854                                                            tool.starterQuantity))
3855                     tool.queueMaterial = None
3856 
3857                 # Queue any blueprints that can be made
3858                 for blueprint in tool.consideredBlueprints:
3859                     haveList = tool.contains
3860                     needList = self.main.materialLib.lib[blueprint]['components']
3861 
3862                     # Check if tool contains blueprint components or no blueprint components required then queue creation
3863                     if self.main.materialLib.lib[blueprint]['components'] == {} or \
3864                             all(haveList.get(k, 0) >= v for k, v in needList.items()):
3865                         if self.main.balance >= self.main.materialLib.lib[blueprint]['cost'] * self.main.opCostModifier \
3866                                 and tool.queueDelay == 0:
3867 
3868                             # Queue creation of materials
3869                             tool.queueMaterial = blueprint
3870                             if tool.type in [STARTER, CRAFTER]:
3871                                 tool.queueDelay = tool.op_time - self.main.opTimeModifierStarterCrafter
3872                             else:
3873                                 tool.queueDelay = tool.op_time - self.main.opTimeModifierTier2Machines
3874                             effectiveCost = self.main.materialLib.lib[blueprint]['cost'] * self.main.opCostModifier
3875                             self.main.updateBalance(self.main.balance - effectiveCost)
3876 
3877                             # Remove blueprint components from container
3878                             for k, v in self.main.materialLib.lib[blueprint]['components'].items():
3879                                 tool.contains[k] -= v
3880                             if tool == self.main.toolPropertiesFrame.selectedTool:  # Update tool inv menu if displayed
3881                                 self.main.toolPropertiesFrame.refreshInventory()
3882 
3883         # Move each piece of material
3884         for piece in self.main.oMaterials:
3885             if piece.pickedUp is False:
3886                 piece.rollerMove()  # Move the piece forward
3887 
3888         # Process any motion animations & movements
3889         for tool in self.main.oMachines:
3890             if tool.type in [ROBOTIC_ARM] and tool.motionInProgress is True:
3891                 tool.processArmMovement()
3892 
3893         # Set action for each piece of material
3894         for piece in self.main.oMaterials:
3895 
3896             if piece.checkIfAtTileCenter():  # Check if on any tool centers
3897 
3898                 for tool in self.main.oMachines:
3899                     if (piece.x, piece.y) == (tool.x, tool.y):  # Material matches a tool center
3900 
3901                         if tool.type in [ROLLER]:
3902                             piece.orientation = tool.orientation
3903 
3904                             # Group and adjust visual offset for near stacking materials
3905                             if piece.groupID is None:
3906                                 nearbyMat = self.main.getAnyNearbyMaterial(piece)
3907                                 # print('Nearby Material Found, Group = %s' % str(nearbyMat.groupID))
3908 
3909                                 if nearbyMat is not None and nearbyMat.groupID is not None:
3910                                     self.main.assignMaterialToGroup(piece, nearbyMat.groupID)
3911                                 else:
3912                                     self.main.assignMaterialToNewGroup(
3913                                         piece)  # Best to assign group to all mats to prevent further searching
3914 
3915                             # Pick up from roller if under arm pickup zone
3916                             for tool in self.main.oMachines:
3917                                 if (piece.x, piece.y) == (tool.xPickUpZone, tool.yPickUpZone):
3918                                     if tool.type in [ROBOTIC_ARM, FILTERED_ARM] and tool.motionInProgress is False:
3919                                         tool.pickUpMaterial(piece)
3920                                         self.main.updateBalance(
3921                                             self.main.balance - tool.op_cost * self.main.opCostModifier)
3922 
3923                         elif tool.type in [SPLITTER_LEFT, SPLITTER_RIGHT, SPLITTER_TEE, SPLITTER_3WAY]:
3924                             tool.splitMaterial(piece)
3925                             self.main.updateBalance(self.main.balance - tool.op_cost * self.main.opCostModifier)
3926 
3927                         elif tool.type in [FILTER_LEFT, FILTER_RIGHT, FILTER_TEE]:
3928                             tool.filterMaterial(piece)
3929                             self.main.updateBalance(self.main.balance - tool.op_cost * self.main.opCostModifier)
3930 
3931                         elif tool.type in [TELEPORTER_INPUT]:
3932                             tool.teleportMaterial(piece)
3933                             self.main.updateBalance(self.main.balance - tool.op_cost * self.main.opCostModifier)
3934 
3935                         elif tool.type in [CRAFTER, DRAWER, CUTTER, FURNACE, PRESS]:
3936                             tool.addMaterialToInventory(piece)
3937                             if tool == self.main.toolPropertiesFrame.selectedTool:  # Update tool inv menu if displayed
3938                                 self.main.toolPropertiesFrame.refreshInventory()
3939                             piece.delMaterial()
3940 
3941                         elif tool.type in [SELLER]:
3942                             self.main.updateBalance(self.main.balance + piece.value)
3943                             self.main.updateEvent('Sold %s for $%s!' % (piece.type, str(piece.value)))
3944                             for i in range(piece.quantity):  # Account for stacks of material entering machine
3945                                 # Default to 0 then add 1
3946                                 self.main.salesCollector[piece.type] = self.main.salesCollector.get(piece.type, 0) + 1
3947                             piece.delMaterial()
3948 
3949                         break  # Exit innermost for loop when machine is found
3950 
3951                 # Check if material fell off rollers
3952                 piece.onFloor = True
3953                 for tool in self.main.oMachines:
3954                     if (piece.x, piece.y) == (tool.x, tool.y):  # Material matches tool center
3955                         piece.onFloor = False
3956                 if piece.onFloor:
3957                     piece.delMaterial()  # Material is not on machine and is removed
3958 
3959         # Check for low balance
3960         if self.main.balance < 100:
3961             self.main.updateMessage('Starters may not be able to afford raw materials! May need to sell machines')
3962 
3963         # Check for game reset flag
3964         if self.main.queueReset:
3965             self.main.reset()
3966 
3967         # Iteration time stabilization
3968         self.main.frameRateResultSet.append(iterationToIterationTime)
3969         self.main.iteration += 1
3970 
3971 
3972 # -------- Main App Functions -------- #
3973 
3974 def exceptHook(type_, value, traceback_):  # Req to return error traceback on PyQt 5.5 including sig/slots
3975     traceback.print_exception(type_, value, traceback_)
3976     QtCore.qFatal('')
3977 
3978 
3979 # -------- Main App -------- #
3980 
3981 if __name__ == '__main__':  # Run if main program, but not if imported this from elsewhere
3982     sys.excepthook = exceptHook  # Req to return error traceback on PyQt 5.5 including sig/slots
3983 
3984     app = QtWidgets.QApplication(sys.argv)
3985     mainApp = clsMainApp()
3986     mainApp.finishSetup()
3987 
3988     sys.exit(app.exec_())
3989 



Python Code - factoryLib.py

3997 from PyQt5 import QtCore, QtWidgets, QtGui
3998 
3999 
4000 class machineLib:
4001     def __init__(self):
4002         self.lib = {
4003             'Starter': {
4004                 'unlock': 0,                # Unlocked by default
4005                 'buildCost': 1000,
4006                 'opCost': 5,
4007                 'opTime': 3,
4008                 'description': 'Launches new basic resources. Each basic resource must be purchased. \ 
4009                                Maximum of 10 Starters can be placed per assembly line without additional research.',
4010                 'imageTop': QtGui.QPixmap('images/Starter.gif'),
4011                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4012                 'imageComposite': QtGui.QPixmap('images/Starter Composite.gif').scaled(40, 40),
4013                 'blueprintType': 'Basic',
4014                 },
4015             'Seller': {
4016                 'unlock': 0,                # Unlocked by default
4017                 'buildCost': 5000,
4018                 'opCost': 0,
4019                 'opTime': 0,
4020                 'description': 'Sells resources.',
4021                 'imageTop': QtGui.QPixmap('images/Seller.gif'),
4022                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4023                 'imageComposite': QtGui.QPixmap('images/Seller Composite.gif').scaled(40, 40),
4024             },
4025             'Crafter': {
4026                 'unlock': 80000,
4027                 'buildCost': 20000,
4028                 'opCost': 5,
4029                 'opTime': 3,
4030                 'description': 'Creates new resources using blueprints.',
4031                 'imageTop': QtGui.QPixmap('images/Crafter.gif'),
4032                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4033                 'imageComposite': QtGui.QPixmap('images/Crafter Composite.gif').scaled(40, 40),
4034                 'blueprintType': 'Tier2',
4035                 },
4036             'Roller': {
4037                 'unlock': 5000,
4038                 'buildCost': 300,
4039                 'opCost': 0,
4040                 'opTime': 0,
4041                 'description': 'Moves resources around the factory.',
4042                 'imageTop': QtGui.QPixmap('images/Blank.gif'),
4043                 'imageBottom': QtGui.QPixmap('images/Roller.gif'),
4044                 'imageComposite': QtGui.QPixmap('images/Roller Composite.gif').scaled(40, 40),
4045                 },
4046             'Drawer': {
4047                 'unlock': 40000,
4048                 'buildCost': 10000,
4049                 'opCost': 5,
4050                 'opTime': 3,
4051                 'description': 'Creates wire by consuming basic resources.',
4052                 'imageTop': QtGui.QPixmap('images/Drawer.gif'),
4053                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4054                 'imageComposite': QtGui.QPixmap('images/Drawer Composite.gif').scaled(40, 40),
4055                 'blueprintType': 'Wire',
4056                 },
4057             'Cutter': {
4058                 'unlock': 30000,
4059                 'buildCost': 10000,
4060                 'opCost': 5,
4061                 'opTime': 3,
4062                 'description': 'Creates gears by consuming basic resources.',
4063                 'imageTop': QtGui.QPixmap('images/Cutter.gif'),
4064                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4065                 'imageComposite': QtGui.QPixmap('images/Cutter Composite.gif').scaled(40, 40),
4066                 'blueprintType': 'Gear',
4067                 },
4068             'Furnace': {
4069                 'unlock': 20000,
4070                 'buildCost': 10000,
4071                 'opCost': 5,
4072                 'opTime': 3,
4073                 'description': 'Creates liquid by consuming basic resources.',
4074                 'imageTop': QtGui.QPixmap('images/Furnace.gif'),
4075                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4076                 'imageComposite': QtGui.QPixmap('images/Furnace Composite.gif').scaled(40, 40),
4077                 'blueprintType': 'Liquid',
4078                 },
4079             'Press': {
4080                 'unlock': 300000,
4081                 'buildCost': 10000,
4082                 'opCost': 5,
4083                 'opTime': 3,
4084                 'description': 'Creates plate by consuming basic resources.',
4085                 'imageTop': QtGui.QPixmap('images/Press.gif'),
4086                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4087                 'imageComposite': QtGui.QPixmap('images/Press Composite.gif').scaled(40, 40),
4088                 'blueprintType': 'Plate',
4089                 },
4090             'Splitter Left': {                                    
4091                 'unlock': 600000,
4092                 'buildCost': 10000,
4093                 'opCost': 5,
4094                 'opTime': 1,
4095                 'description': 'Splits incoming material in different directions',
4096                 'imageTop': QtGui.QPixmap('images/Splitter Left.gif'),
4097                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4098                 'imageComposite': QtGui.QPixmap('images/Splitter Left Composite.gif').scaled(40, 40),
4099                 },
4100             'Splitter Right': {                                    
4101                 'unlock': 600000,
4102                 'buildCost': 10000,
4103                 'opCost': 5,
4104                 'opTime': 1,
4105                 'description': 'Splits incoming material in different directions',
4106                 'imageTop': QtGui.QPixmap('images/Splitter Right.gif'),
4107                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4108                 'imageComposite': QtGui.QPixmap('images/Splitter Right Composite.gif').scaled(40, 40),
4109                 },
4110             'Splitter Tee': {                                    
4111                 'unlock': 600000,
4112                 'buildCost': 10000,
4113                 'opCost': 5,
4114                 'opTime': 1,
4115                 'description': 'Splits incoming material in different directions',
4116                 'imageTop': QtGui.QPixmap('images/Splitter Tee.gif'),
4117                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4118                 'imageComposite': QtGui.QPixmap('images/Splitter Tee Composite.gif').scaled(40, 40),
4119                 },
4120             'Splitter 3-Way': {                                    
4121                 'unlock': 1000000,
4122                 'buildCost': 10000,
4123                 'opCost': 5,
4124                 'opTime': 1,
4125                 'description': 'Splits incoming material in different directions',
4126                 'imageTop': QtGui.QPixmap('images/Splitter 3-Way.gif'),
4127                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4128                 'imageComposite': QtGui.QPixmap('images/Splitter 3-Way Composite.gif').scaled(40, 40),
4129                 },
4130             'Filter Left': {
4131                 'unlock': 300000,
4132                 'buildCost': 10000,
4133                 'opCost': 2,
4134                 'opTime': 1,
4135                 'description': 'Splits material based on filter selection.',
4136                 'imageTop': QtGui.QPixmap('images/Filter Left.gif'),
4137                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4138                 'imageComposite': QtGui.QPixmap('images/Filter Left Composite.gif').scaled(40, 40),
4139                 },
4140             'Filter Right': {
4141                 'unlock': 500000,
4142                 'buildCost': 10000,
4143                 'opCost': 2,
4144                 'opTime': 1,
4145                 'description': 'Splits material based on filter selection.',
4146                 'imageTop': QtGui.QPixmap('images/Filter Right.gif'),
4147                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4148                 'imageComposite': QtGui.QPixmap('images/Filter Right Composite.gif').scaled(40, 40),
4149                 },
4150             'Filter Tee': {
4151                 'unlock': 500000,
4152                 'buildCost': 10000,
4153                 'opCost': 2,
4154                 'opTime': 1,
4155                 'description': 'Splits material based on filter selection.',
4156                 'imageTop': QtGui.QPixmap('images/Filter Tee.gif'),
4157                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4158                 'imageComposite': QtGui.QPixmap('images/Filter Tee Composite.gif').scaled(40, 40),
4159                 },
4160             'Robotic Arm': {
4161                 'unlock': 400000,
4162                 'buildCost': 10000,
4163                 'opCost': 2,
4164                 'opTime': 1,
4165                 'description': 'Picks up material and moves it drop off zone.',
4166                 'imageTop': QtGui.QPixmap('images/Blank.gif'),
4167                 'imageBottom': QtGui.QPixmap('images/Robotic Arm Base.gif'),
4168                 'imageComposite': QtGui.QPixmap('images/Robotic Arm Composite.gif').scaled(40, 40),
4169                 'imageLink1': QtGui.QPixmap('images/Robotic Arm Link 1.gif'),
4170                 'imageLink2': QtGui.QPixmap('images/Robotic Arm Link 2.gif'),
4171                 },
4172             'Filtered Arm': {
4173                 'unlock': 500000,
4174                 'buildCost': 10000,
4175                 'opCost': 2,
4176                 'opTime': 1,
4177                 'description': 'Picks up selected material type and moves it drop off zone.',
4178                 'imageTop': QtGui.QPixmap('images/Blank.gif'),
4179                 'imageBottom': QtGui.QPixmap('images/Filtered Arm Base.gif'),
4180                 'imageComposite': QtGui.QPixmap('images/Filtered Arm Composite.gif').scaled(40, 40),
4181                 'imageLink1': QtGui.QPixmap('images/Robotic Arm Link 1.gif'),
4182                 'imageLink2': QtGui.QPixmap('images/Robotic Arm Link 2.gif'),
4183                 },
4184             'Teleporter Input': {
4185                 'unlock': 250000000,
4186                 'buildCost': 1000000,
4187                 'opCost': 100,
4188                 'opTime': 1,
4189                 'description': 'Teleports materials to the Teleporter Output with matching ID.',
4190                 'imageTop': QtGui.QPixmap('images/Teleporter Input.gif'),
4191                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4192                 'imageComposite': QtGui.QPixmap('images/Teleporter Input.gif').scaled(40, 40),
4193                 },
4194             'Teleporter Output': {
4195                 'unlock': 250000000,
4196                 'buildCost': 1000000,
4197                 'opCost': 100,
4198                 'opTime': 1,
4199                 'description': 'Receives teleported materials from the Teleporter Input with matching ID.',
4200                 'imageTop': QtGui.QPixmap('images/Teleporter Output.gif'),
4201                 'imageBottom': QtGui.QPixmap('images/MachineBottom.gif'),
4202                 'imageComposite': QtGui.QPixmap('images/Teleporter Output.gif').scaled(40, 40),
4203                 },
4204             }
4205 
4206 
4207 class materialLib:
4208     def __init__(self):
4209         self.lib = {
4210             # Basic
4211             'Copper': {
4212                 'value': 80,
4213                 'cost': 5,
4214                 'class': 'Basic',
4215                 'image': QtGui.QPixmap('images/Copper.gif'),
4216                 'image_qty_2': QtGui.QPixmap('images/Copper_2.gif'),
4217                 'image_qty_3': QtGui.QPixmap('images/Copper_3.gif'),
4218                 'maker': 'Starter',
4219                 'unlock': 0,
4220                 'components': {},
4221                 },
4222             'Gold': {
4223                 'value': 80,
4224                 'cost': 5,
4225                 'class': 'Basic',
4226                 'image': QtGui.QPixmap('images/Gold.gif'),
4227                 'image_qty_2': QtGui.QPixmap('images/Gold_2.gif'),
4228                 'image_qty_3': QtGui.QPixmap('images/Gold_3.gif'),
4229                 'maker': 'Starter',
4230                 'unlock': 0,
4231                 'components': {},
4232                 },
4233             'Iron': {
4234                 'value': 80,
4235                 'cost': 5,
4236                 'class': 'Basic',
4237                 'color': 'seashell4',
4238                 'image': QtGui.QPixmap('images/Iron.gif'),
4239                 'image_qty_2': QtGui.QPixmap('images/Iron_2.gif'),
4240                 'image_qty_3': QtGui.QPixmap('images/Iron_3.gif'),
4241                 'maker': 'Starter',
4242                 'unlock': 0,
4243                 'components': {},
4244                 },
4245             'Aluminum': {
4246                 'value': 80,
4247                 'cost': 5,
4248                 'class': 'Basic',
4249                 'image': QtGui.QPixmap('images/Aluminum.gif'),
4250                 'image_qty_2': QtGui.QPixmap('images/Aluminum_2.gif'),
4251                 'image_qty_3': QtGui.QPixmap('images/Aluminum_3.gif'),
4252                 'maker': 'Starter',
4253                 'unlock': 0,
4254                 'components': {},
4255                 },
4256             'Crystal': {
4257                 'value': 80,
4258                 'cost': 5,
4259                 'class': 'Basic',
4260                 'image': QtGui.QPixmap('images/Crystal.gif'),
4261                 'image_qty_2': QtGui.QPixmap('images/Crystal_2.gif'),
4262                 'image_qty_3': QtGui.QPixmap('images/Crystal_3.gif'),
4263                 'maker': 'Starter',
4264                 'unlock': 0,
4265                 'components': {},
4266                 },
4267             # Wire
4268             'Copper Wire': {
4269                 'value': 100,
4270                 'cost': 0,
4271                 'image': QtGui.QPixmap('images/Copper Wire.gif'),
4272                 'class': 'Wire',
4273                 'maker': 'Drawer',
4274                 'unlock': 0,
4275                 'components': {
4276                     'Copper': 1
4277                     },
4278                 },
4279             'Gold Wire': {
4280                 'value': 100,
4281                 'cost': 0,
4282                 'image': QtGui.QPixmap('images/Gold Wire.gif'),
4283                 'class': 'Wire',
4284                 'maker': 'Drawer',
4285                 'unlock': 0,
4286                 'components': {
4287                     'Gold': 1
4288                     },
4289                 },
4290             'Iron Wire': {
4291                 'value': 100,
4292                 'cost': 0,
4293                 'image': QtGui.QPixmap('images/Iron Wire.gif'),
4294                 'class': 'Wire',
4295                 'maker': 'Drawer',
4296                 'unlock': 0,
4297                 'components': {
4298                     'Iron': 1
4299                     },
4300                 },
4301             'Aluminum Wire': {
4302                 'value': 100,
4303                 'cost': 0,
4304                 'image': QtGui.QPixmap('images/Aluminum Wire.gif'),
4305                 'class': 'Wire',
4306                 'maker': 'Drawer',
4307                 'unlock': 0,
4308                 'components': {
4309                     'Aluminum': 1
4310                     },
4311                 },
4312             'Crystal Wire': {
4313                 'value': 100,
4314                 'cost': 0,
4315                 'image': QtGui.QPixmap('images/Crystal Wire.gif'),
4316                 'class': 'Wire',
4317                 'maker': 'Drawer',
4318                 'unlock': 0,
4319                 'components': {
4320                     'Crystal': 1
4321                     },
4322                 },
4323             # Gear
4324             'Copper Gear': {
4325                 'value': 100,
4326                 'cost': 0,
4327                 'image': QtGui.QPixmap('images/Copper Gear.gif'),
4328                 'class': 'Gear',
4329                 'maker': 'Cutter',
4330                 'unlock': 0,
4331                 'components': {
4332                     'Copper': 1
4333                     },
4334                 },
4335             'Gold Gear': {
4336                 'value': 100,
4337                 'cost': 0,
4338                 'image': QtGui.QPixmap('images/Gold Gear.gif'),
4339                 'class': 'Gear',
4340                 'maker': 'Cutter',
4341                 'unlock': 0,
4342                 'components': {
4343                     'Gold': 1
4344                     },
4345                 },
4346             'Iron Gear': {
4347                 'value': 100,
4348                 'cost': 0,
4349                 'image': QtGui.QPixmap('images/Iron Gear.gif'),
4350                 'class': 'Gear',
4351                 'maker': 'Cutter',
4352                 'unlock': 0,
4353                 'components': {
4354                     'Iron': 1
4355                     },
4356                 },
4357             'Aluminum Gear': {
4358                 'value': 100,
4359                 'cost': 0,
4360                 'image': QtGui.QPixmap('images/Aluminum Gear.gif'),
4361                 'class': 'Gear',
4362                 'maker': 'Cutter',
4363                 'unlock': 0,
4364                 'components': {
4365                     'Aluminum': 1
4366                     },
4367                 },
4368             'Crystal Gear': {
4369                 'value': 100,
4370                 'cost': 0,
4371                 'image': QtGui.QPixmap('images/Crystal Gear.gif'),
4372                 'class': 'Gear',
4373                 'maker': 'Cutter',
4374                 'unlock': 0,
4375                 'components': {
4376                     'Crystal': 1
4377                     },
4378                 },
4379             # Liquid
4380             'Molten Copper': {
4381                 'value': 100,
4382                 'cost': 0,
4383                 'image': QtGui.QPixmap('images/Molten Copper.gif'),
4384                 'class': 'Liquid',
4385                 'maker': 'Furnace',
4386                 'unlock': 0,
4387                 'components': {
4388                     'Copper': 1
4389                     },
4390                 },
4391             'Molten Gold': {
4392                 'value': 100,
4393                 'cost': 0,
4394                 'image': QtGui.QPixmap('images/Molten Gold.gif'),
4395                 'class': 'Liquid',
4396                 'maker': 'Furnace',
4397                 'unlock': 0,
4398                 'components': {
4399                     'Gold': 1
4400                     },
4401                 },
4402             'Molten Iron': {
4403                 'value': 100,
4404                 'cost': 0,
4405                 'image': QtGui.QPixmap('images/Molten Iron.gif'),
4406                 'class': 'Liquid',
4407                 'maker': 'Furnace',
4408                 'unlock': 0,
4409                 'components': {
4410                     'Iron': 1
4411                     },
4412                 },
4413             'Molten Aluminum': {
4414                 'value': 100,
4415                 'cost': 0,
4416                 'image': QtGui.QPixmap('images/Molten Aluminum.gif'),
4417                 'class': 'Liquid',
4418                 'maker': 'Furnace',
4419                 'unlock': 0,
4420                 'components': {
4421                     'Aluminum': 1
4422                     },
4423                 },
4424             'Molten Crystal': {
4425                 'value': 100,
4426                 'cost': 0,
4427                 'image': QtGui.QPixmap('images/Molten Crystal.gif'),
4428                 'class': 'Liquid',
4429                 'maker': 'Furnace',
4430                 'unlock': 0,
4431                 'components': {
4432                     'Crystal': 1
4433                     },
4434                 },
4435             # Plate
4436             'Copper Plate': {
4437                 'value': 100,
4438                 'cost': 0,
4439                 'image': QtGui.QPixmap('images/Copper Plate.gif'),
4440                 'class': 'Plate',
4441                 'maker': 'Press',
4442                 'unlock': 0,
4443                 'components': {
4444                     'Copper': 1
4445                     },
4446                 },
4447             'Gold Plate': {
4448                 'value': 100,
4449                 'cost': 0,
4450                 'image': QtGui.QPixmap('images/Gold Plate.gif'),
4451                 'class': 'Plate',
4452                 'maker': 'Press',
4453                 'unlock': 0,
4454                 'components': {
4455                     'Gold': 1
4456                     },
4457                 },
4458             'Iron Plate': {
4459                 'value': 100,
4460                 'cost': 0,
4461                 'image': QtGui.QPixmap('images/Iron Plate.gif'),
4462                 'class': 'Plate',
4463                 'maker': 'Press',
4464                 'unlock': 0,
4465                 'components': {
4466                     'Iron': 1
4467                     },
4468                 },
4469             'Aluminum Plate': {
4470                 'value': 100,
4471                 'cost': 0,
4472                 'image': QtGui.QPixmap('images/Aluminum Plate.gif'),
4473                 'class': 'Plate',
4474                 'maker': 'Press',
4475                 'unlock': 0,
4476                 'components': {
4477                     'Aluminum': 1
4478                     },
4479                 },
4480             'Crystal Plate': {
4481                 'value': 100,
4482                 'cost': 0,
4483                 'image': QtGui.QPixmap('images/Crystal Plate.gif'),
4484                 'class': 'Plate',
4485                 'maker': 'Press',
4486                 'unlock': 0,
4487                 'components': {
4488                     'Crystal': 1
4489                     },
4490                 },
4491             # Tier2
4492             'Circuit ': {                                # Unlocked by default for free
4493                 'value': 350,
4494                 'cost': 0,
4495                 'image': QtGui.QPixmap('images/Circuit.gif'),
4496                 'class': 'Tier2',
4497                 'maker': 'Crafter',
4498                 'unlock': 0,
4499                 'components': {
4500                     'Copper Wire': 2,
4501                     'Gold': 1
4502                     },
4503                 },
4504             'Engine': {
4505                 'value': 400,
4506                 'cost': 0,
4507                 'image': QtGui.QPixmap('images/Engine.gif'),
4508                 'class': 'Tier2',
4509                 'maker': 'Crafter',
4510                 'unlock': 360000,
4511                 'components': {
4512                     'Iron Gear': 2,
4513                     'Gold Gear': 1
4514                     },
4515                 },
4516             'Heating Coil': {
4517                 'value': 350,
4518                 'cost': 0,
4519                 'image': QtGui.QPixmap('images/Heating Coil.gif'),
4520                 'class': 'Tier2',
4521                 'maker': 'Crafter',
4522                 'unlock': 360000,
4523                 'components': {
4524                     'Circuit ': 1,
4525                     'Aluminum': 2
4526                     },
4527                 },
4528             'Cooling Coil': {
4529                 'value': 350,
4530                 'cost': 0,
4531                 'image': QtGui.QPixmap('images/Cooling Coil.gif'),
4532                 'class': 'Tier2',
4533                 'maker': 'Crafter',
4534                 'unlock': 360000,
4535                 'components': {
4536                     'Crystal': 1,
4537                     'Gold': 1,
4538                     'Gold Wire': 1
4539                     },
4540                 },
4541             'Light Bulb': {
4542                 'value': 350,
4543                 'cost': 5,
4544                 'image': QtGui.QPixmap('images/Light Bulb.gif'),
4545                 'class': 'Tier2',
4546                 'maker': 'Crafter',
4547                 'unlock': 360000,
4548                 'components': {
4549                     'Cooling Coil': 1,
4550                     'Gold': 1,
4551                     'Aluminum': 1
4552                     },
4553                 },
4554             'Clock': {
4555                 'value': 500,
4556                 'cost': 0,
4557                 'image': QtGui.QPixmap('images/Clock.gif'),
4558                 'class': 'Tier2',
4559                 'maker': 'Crafter',
4560                 'unlock': 540000,
4561                 'components': {
4562                     'Iron': 2,
4563                     'Gold': 2,
4564                     'Copper Gear': 1,
4565                     },
4566                 },
4567             'Antenna': {
4568                 'value': 500,
4569                 'cost': 0,
4570                 'image': QtGui.QPixmap('images/Antenna.gif'),
4571                 'class': 'Tier2',
4572                 'maker': 'Crafter',
4573                 'unlock': 540000,
4574                 'components': {
4575                     'Crystal Wire': 4,
4576                     'Iron': 1
4577                     },
4578                 },
4579             'Grill': {
4580                 'value': 600,
4581                 'cost': 0,
4582                 'image': QtGui.QPixmap('images/Grill.gif'),
4583                 'class': 'Tier2',
4584                 'maker': 'Crafter',
4585                 'unlock': 600000,
4586                 'components': {
4587                     'Heating Coil': 1,
4588                     'Iron': 4
4589                     },
4590                 },
4591             'Toaster': {
4592                 'value': 900,
4593                 'cost': 0,
4594                 'image': QtGui.QPixmap('images/Toaster.gif'),
4595                 'class': 'Tier2',
4596                 'maker': 'Crafter',
4597                 'unlock': 900000,
4598                 'components': {
4599                     'Heating Coil': 1,
4600                     'Aluminum': 1,
4601                     'Copper': 1
4602                     },
4603                 },
4604             'Air Conditioner': {
4605                 'value': 900,
4606                 'cost': 0,
4607                 'image': QtGui.QPixmap('images/Air Conditioner.gif'),
4608                 'class': 'Tier2',
4609                 'maker': 'Crafter',
4610                 'unlock': 900000,
4611                 'components': {
4612                     'Circuit ': 1,
4613                     'Aluminum': 2
4614                     },
4615                 },
4616             'Battery': {
4617                 'value': 1000,
4618                 'cost': 0,
4619                 'image': QtGui.QPixmap('images/Battery.gif'),
4620                 'class': 'Tier2',
4621                 'maker': 'Crafter',
4622                 'unlock': 1050000,
4623                 'components': {
4624                     'Circuit ': 1,
4625                     'Aluminum': 1,
4626                     'Molten Aluminum': 1
4627                     },
4628                 },
4629             'Washing Machine': {
4630                 'value': 1100,
4631                 'cost': 0,
4632                 'image': QtGui.QPixmap('images/Washing Machine.gif'),
4633                 'class': 'Tier2',
4634                 'maker': 'Crafter',
4635                 'unlock': 1100000,
4636                 'components': {
4637                     'Engine': 1,
4638                     'Aluminum': 2,
4639                     'Copper': 2
4640                     },
4641                 },
4642             'Solar Panel': {
4643                 'value': 1200,
4644                 'cost': 0,
4645                 'image': QtGui.QPixmap('images/Solar Panel.gif'),
4646                 'class': 'Tier2',
4647                 'maker': 'Crafter',
4648                 'unlock': 1170000,
4649                 'components': {
4650                     'Circuit ': 1,
4651                     'Gold': 2,
4652                     'Crystal': 1
4653                     },
4654                 },
4655             'Headphones': {
4656                 'value': 1300,
4657                 'cost': 0,
4658                 'image': QtGui.QPixmap('images/Headphones.gif'),
4659                 'class': 'Tier2',
4660                 'maker': 'Crafter',
4661                 'unlock': 1300000,
4662                 'components': {
4663                     'Circuit ': 1,
4664                     'Gold Wire': 1,
4665                     'Crystal Wire': 1
4666                     },
4667                 },
4668             'Processor': {
4669                 'value': 1300,
4670                 'cost': 0,
4671                 'image': QtGui.QPixmap('images/Processor.gif'),
4672                 'class': 'Tier2',
4673                 'maker': 'Crafter',
4674                 'unlock': 1320000,
4675                 'components': {
4676                     'Circuit ': 2,
4677                     'Aluminum': 2
4678                     },
4679                 },
4680             'Drill': {
4681                 'value': 1500,
4682                 'cost': 0,
4683                 'image': QtGui.QPixmap('images/Drill.gif'),
4684                 'class': 'Tier2',
4685                 'maker': 'Crafter',
4686                 'unlock': 1500000,
4687                 'components': {
4688                     'Crystal': 2,
4689                     'Copper Gear': 2,
4690                     'Engine': 1
4691                     },
4692                 },
4693             'Power Supply': {
4694                 'value': 2000,
4695                 'cost': 5,
4696                 'image': QtGui.QPixmap('images/Power Supply.gif'),
4697                 'class': 'Tier2',
4698                 'maker': 'Crafter',
4699                 'unlock': 1920000,
4700                 'components': {
4701                     'Circuit ': 1,
4702                     'Copper Wire': 3,
4703                     'Iron Wire': 3
4704                     },
4705                 },
4706             'Speaker': {
4707                 'value': 3300,
4708                 'cost': 0,
4709                 'image': QtGui.QPixmap('images/Speaker.gif'),
4710                 'class': 'Tier2',
4711                 'maker': 'Crafter',
4712                 'unlock': 3300000,
4713                 'components': {
4714                     'Circuit ': 2,
4715                     'Gold Wire': 4,
4716                     'Crystal Wire': 4
4717                     },
4718                 },
4719             'Radio': {
4720                 'value': 5600,
4721                 'cost': 0,
4722                 'image': QtGui.QPixmap('images/Radio.gif'),
4723                 'class': 'Tier2',
4724                 'maker': 'Crafter',
4725                 'unlock': 5670000,
4726                 'components': {
4727                     'Circuit ': 1,
4728                     'Antenna': 1,
4729                     'Battery': 1
4730                     },
4731                 },
4732             'Jack Hammer': {
4733                 'value': 7000,
4734                 'cost': 0,
4735                 'image': QtGui.QPixmap('images/Jack Hammer.gif'),
4736                 'class': 'Tier2',
4737                 'maker': 'Crafter',
4738                 'unlock': 6920000,
4739                 'components': {
4740                     'Circuit ': 4,
4741                     'Crystal': 4,
4742                     'Iron Plate': 4
4743                     },
4744                 },
4745             'TV': {
4746                 'value': 4000,
4747                 'cost': 5,
4748                 'image': QtGui.QPixmap('images/TV.gif'),
4749                 'class': 'Tier2',
4750                 'maker': 'Crafter',
4751                 'unlock': 7100000,
4752                 'components': {
4753                     'Power Supply': 1,
4754                     'Circuit ': 1,
4755                     'Aluminum': 4
4756                     },
4757                 },
4758             'Smartphone': {
4759                 'value': 7300,
4760                 'cost': 0,
4761                 'image': QtGui.QPixmap('images/Smartphone.gif'),
4762                 'class': 'Tier2',
4763                 'maker': 'Crafter',
4764                 'unlock': 7300000,
4765                 'components': {
4766                     'Processor': 1,
4767                     'Battery': 1,
4768                     'Aluminum': 2
4769                     },
4770                 },
4771             'Refrigerator': {
4772                 'value': 7400,
4773                 'cost': 0,
4774                 'image': QtGui.QPixmap('images/Refrigerator.gif'),
4775                 'class': 'Tier2',
4776                 'maker': 'Crafter',
4777                 'unlock': 7400000,
4778                 'components': {
4779                     'Cooling Coil': 1,
4780                     'Aluminum': 6,
4781                     'Power Supply': 1
4782                     },
4783                 },
4784             'Tablet': {
4785                 'value': 7600,
4786                 'cost': 0,
4787                 'image': QtGui.QPixmap('images/Tablet.gif'),
4788                 'class': 'Tier2',
4789                 'maker': 'Crafter',
4790                 'unlock': 7600000,
4791                 'components': {
4792                     'Processor': 1,
4793                     'Battery': 2,
4794                     'Aluminum': 2
4795                     },
4796                 },
4797             'Microwave': {
4798                 'value': 8000,
4799                 'cost': 0,
4800                 'image': QtGui.QPixmap('images/Microwave.gif'),
4801                 'class': 'Tier2',
4802                 'maker': 'Crafter',
4803                 'unlock': 8070000,
4804                 'components': {
4805                     'Heating Coil': 5,
4806                     'Crystal Plate': 5,
4807                     'Aluminum Plate': 5
4808                     },
4809                 },
4810             'Railroad Tracks': {
4811                 'value': 8400,
4812                 'cost': 0,
4813                 'image': QtGui.QPixmap('images/Railroad Tracks.gif'),
4814                 'class': 'Tier2',
4815                 'maker': 'Crafter',
4816                 'unlock': 8400000,
4817                 'components': {
4818                     'Iron': 10,
4819                     'Iron Plate': 10
4820                     },
4821                 },
4822             'Smart Watch': {
4823                 'value': 10200,
4824                 'cost': 0,
4825                 'image': QtGui.QPixmap('images/Smart Watch.gif'),
4826                 'class': 'Tier2',
4827                 'maker': 'Crafter',
4828                 'unlock': 10000000,
4829                 'components': {
4830                     'Processor': 2,
4831                     'Iron Plate': 1,
4832                     'Aluminum Plate': 2,
4833                 },
4834             },
4835             'Server Rack': {
4836                 'value': 10600,
4837                 'cost': 0,
4838                 'image': QtGui.QPixmap('images/Server Rack.gif'),
4839                 'class': 'Tier2',
4840                 'maker': 'Crafter',
4841                 'unlock': 11000000,
4842                 'components': {
4843                     'Aluminum Plate': 20,
4844                     'Aluminum': 10
4845                 },
4846             },
4847             'Computer': {
4848                 'value': 11000,
4849                 'cost': 0,
4850                 'image': QtGui.QPixmap('images/Computer.gif'),
4851                 'class': 'Tier2',
4852                 'maker': 'Crafter',
4853                 'unlock': 11000000,
4854                 'components': {
4855                     'Processor': 1,
4856                     'Aluminum': 6,
4857                     'Power Supply': 1
4858                 },
4859             },
4860             'Generator': {
4861                 'value': 12000,
4862                 'cost': 0,
4863                 'image': QtGui.QPixmap('images/Generator.gif'),
4864                 'class': 'Tier2',
4865                 'maker': 'Crafter',
4866                 'unlock': 12000000,
4867                 'components': {
4868                     'Engine': 4,
4869                     'Copper Plate': 5,
4870                     'Gold Plate': 5
4871                     },
4872                 },
4873             'Water Heater': {
4874                 'value': 13000,
4875                 'cost': 0,
4876                 'image': QtGui.QPixmap('images/Water Heater.gif'),
4877                 'class': 'Tier2',
4878                 'maker': 'Crafter',
4879                 'unlock': 13000000,
4880                 'components': {
4881                     'Heating Coil': 5,
4882                     'Crystal Plate': 5,
4883                     'Aluminum Plate': 5
4884                     },
4885                 },
4886             'Drone': {
4887                 'value': 17200,
4888                 'cost': 0,
4889                 'image': QtGui.QPixmap('images/Drone.gif'),
4890                 'class': 'Tier2',
4891                 'maker': 'Crafter',
4892                 'unlock': 17000000,
4893                 'components': {
4894                     'Battery': 2,
4895                     'Processor': 2,
4896                     'Aluminum Plate': 4
4897                     },
4898                 },
4899             'Circuit Board Assembly': {
4900                 'value': 27000,
4901                 'cost': 0,
4902                 'image': QtGui.QPixmap('images/Circuit Board Assembly.gif'),
4903                 'class': 'Tier2',
4904                 'maker': 'Crafter',
4905                 'unlock': 27000000,
4906                 'components': {
4907                     'Circuit ': 20,
4908                     'Copper Plate': 6,
4909                     'Iron Plate': 6
4910                     },
4911                 },
4912             'Oven': {
4913                 'value': 27300,
4914                 'cost': 0,
4915                 'image': QtGui.QPixmap('images/Oven.gif'),
4916                 'class': 'Tier2',
4917                 'maker': 'Crafter',
4918                 'unlock': 27000000,
4919                 'components': {
4920                     'Heating Coil': 10,
4921                     'Iron Plate': 10,
4922                     'Iron': 10
4923                     },
4924                 },
4925             'Laser': {
4926                 'value': 32000,
4927                 'cost': 0,
4928                 'image': QtGui.QPixmap('images/Laser.gif'),
4929                 'class': 'Tier2',
4930                 'maker': 'Crafter',
4931                 'unlock': 32000000,
4932                 'components': {
4933                     'Battery': 6,
4934                     'Crystal Plate': 10,
4935                     'Circuit ': 6
4936                     },
4937                 },
4938             'Advanced Engine': {
4939                 'value': 70000,
4940                 'cost': 0,
4941                 'image': QtGui.QPixmap('images/Advanced Engine.gif'),
4942                 'class': 'Tier2',
4943                 'maker': 'Crafter',
4944                 'unlock': 70000000,
4945                 'components': {
4946                     'Engine': 50,
4947                     'Circuit ': 50
4948                     },
4949                 },
4950             'Electric Generator': {
4951                 'value': 470000,
4952                 'cost': 0,
4953                 'image': QtGui.QPixmap('images/Electric Generator.gif'),
4954                 'class': 'Tier2',
4955                 'maker': 'Crafter',
4956                 'unlock': 470000000,
4957                 'components': {
4958                     'Generator': 15,
4959                     'Circuit ': 50,
4960                     'Battery': 40
4961                     },
4962                 },
4963             'Super Computer': {
4964                 'value': 550000,
4965                 'cost': 0,
4966                 'image': QtGui.QPixmap('images/Super Computer.gif'),
4967                 'class': 'Tier2',
4968                 'maker': 'Crafter',
4969                 'unlock': 550000000,
4970                 'components': {
4971                     'Computer': 30,
4972                     'Server Rack': 10
4973                     },
4974                 },
4975             'Electric Engine': {
4976                 'value': 900000,
4977                 'cost': 0,
4978                 'image': QtGui.QPixmap('images/Electric Engine.gif'),
4979                 'class': 'Tier2',
4980                 'maker': 'Crafter',
4981                 'unlock': 900000000,
4982                 'components': {
4983                     'Advanced Engine': 10,
4984                     'Battery': 40
4985                     },
4986                 },
4987             'AI Processor': {
4988                 'value': 2500000,
4989                 'cost': 0,
4990                 'image': QtGui.QPixmap('images/AI Processor.gif'),
4991                 'class': 'Tier2',
4992                 'maker': 'Crafter',
4993                 'unlock': 2500000000,
4994                 'components': {
4995                     'Super Computer': 4,
4996                     'Circuit ': 40
4997                     },
4998                 },
4999             'AI Robot Body': {
5000                 'value': 2800000,
5001                 'cost': 0,
5002                 'image': QtGui.QPixmap('images/AI Robot Body.gif'),
5003                 'class': 'Tier2',
5004                 'maker': 'Crafter',
5005                 'unlock': 2800000000,
5006                 'components': {
5007                     'Electric Engine': 1,
5008                     'Electric Generator': 1,
5009                     'Aluminum': 400
5010                     },
5011                 },
5012             'AI Robot Head': {
5013                 'value': 5000000,
5014                 'cost': 0,
5015                 'image': QtGui.QPixmap('images/AI Robot Head.gif'),
5016                 'class': 'Tier2',
5017                 'maker': 'Crafter',
5018                 'unlock': 5000000000,
5019                 'components': {
5020                     'AI Processor': 1,
5021                     'Aluminum': 200
5022                     },
5023                 },
5024             'AI Robot': {
5025                 'value': 15000000,
5026                 'cost': 0,
5027                 'image': QtGui.QPixmap('images/AI Robot.gif'),
5028                 'class': 'Tier2',
5029                 'maker': 'Crafter',
5030                 'unlock': 15000000000,
5031                 'components': {
5032                     'AI Robot Body': 1,
5033                     'AI Robot Head': 1
5034                     },
5035                 },
5036             }
5037 
5038 
5039 class achievementLib:
5040     def __init__(self):
5041         self.lib = {
5042             'Sell All Items': {
5043                 'description': 'Sell one of every item',
5044             },
5045             'Max Assembly Lines': {
5046                 'description': 'Unlock all assembly lines',
5047             },
5048             'Unlock All Research': {
5049                 'description': 'Unlock all research upgrades',
5050             },
5051             'Profit I': {
5052                 'description': 'Make $1M per second',
5053             },
5054             'Profit II': {
5055                 'description': 'Make $10M per second',
5056             },
5057             'Profit III': {
5058                 'description': 'Make $100M per second',
5059             },
5060             'Scale I': {
5061                 'description': 'Craft 1 items per second',
5062             },
5063             'Scale II': {
5064                 'description': 'Craft 5 items per second',
5065             },
5066             'Scale III': {
5067                 'description': 'Craft 10 items per second',
5068             },
5069         }
5070 
5071 class researchLib:
5072     def __init__(self):
5073         self.lib = {
5074             'floorPlansFeatureUnlock': {
5075                 'cost': 5000000,
5076                 'description': 'Unlock use of floor plans to save and place machine layouts.',
5077                 'type': 'floorPlanFeature',
5078                 'amount': 1,
5079             },
5080             'OpCostReduc_1': {
5081                 'cost': 150000,
5082                 'description': 'Reduce the cost of electricity for each machine operation by 2.',
5083                 'type': 'opCostModifier',
5084                 'amount': 5,
5085             },
5086             'OpCostReduc_2': {
5087                 'cost': 300000,
5088                 'description': 'Reduce the cost of electricity for each machine operation by 2.',
5089                 'type': 'opCostModifier',
5090                 'amount': 5,
5091             },
5092             # Offsets tool standard op_time of 3s by subtracting, not a multiplier.
5093             'StarterCrafterOpTime_1': {
5094                 'cost': 2000000,
5095                 'description': 'Decrease Starter and Crafter operation time by 1 second.',
5096                 'type': 'opTimeStarterCrafter',
5097                 'amount': 1,
5098             },
5099             # Offsets tool standard op_time of 3s by subtracting, not a multiplier.
5100             'StarterCrafterOpTime_2': {
5101                 'cost': 20000000,
5102                 'description': 'Decrease Starter and Crafter operation time by 1 second.',
5103                 'type': 'opTimeStarterCrafter',
5104                 'amount': 1,
5105             },
5106             # Offsets tool standard op_time of 3s by subtracting, not a multiplier.
5107             'opTimeTier2Machines_1': {
5108                 'cost': 1000000,
5109                 'description': 'Decrease Drawer, Cutter, Furnace, Press and Crafter operation time by 1 second.',
5110                 'type': 'opTimeTier2Machines',
5111                 'amount': 1,
5112             },
5113             # Offsets tool standard op_time of 3s by subtracting, not a multiplier.
5114             'opTimeTier2Machines_2': {
5115                 'cost': 10000000,
5116                 'description': 'Decrease Drawer, Cutter, Furnace, Press and Crafter operation time by 1 second.',
5117                 'type': 'opTimeTier2Machines',
5118                 'amount': 1,
5119             },
5120             'MaxStarters_1': {
5121                 'cost': 100000,
5122                 'description': 'Increase max amount of Starters by 5.',
5123                 'type': 'maxStarters',
5124                 'amount': 5,
5125             },
5126             'MaxStarters_2': {
5127                 'cost': 2500000,
5128                 'description': 'Increase max amount of Starters by 5.',
5129                 'type': 'maxStarters',
5130                 'amount': 5,
5131             },
5132             'MaxStarters_3': {
5133                 'cost': 5000000,
5134                 'description': 'Increase max amount of Starters by 5.',
5135                 'type': 'maxStarters',
5136                 'amount': 5,
5137             },
5138             'MaxStarters_4': {
5139                 'cost': 10000000,
5140                 'description': 'Increase max amount of Starters by 10.',
5141                 'type': 'maxStarters',
5142                 'amount': 10,
5143             },
5144             'MaxStarters_5': {
5145                 'cost': 100000000,
5146                 'description': 'Increase max amount of Starters by 10.',
5147                 'type': 'maxStarters',
5148                 'amount': 10,
5149             },
5150             'MaxStarters_6': {
5151                 'cost': 1000000000,
5152                 'description': 'Increase max amount of Starters by 10.',
5153                 'type': 'maxStarters',
5154                 'amount': 10,
5155             },
5156             'MaxTransporters_1': {
5157                 'cost': 50000000000,
5158                 'description': 'Increase max amount of Transporters by 5.',
5159                 'type': 'maxTransporters',
5160                 'amount': 5,
5161             },
5162             'MaxTransporters_2': {
5163                 'cost': 80000000000,
5164                 'description': 'Increase max amount of Transporters by 5.',
5165                 'type': 'maxTransporters',
5166                 'amount': 5,
5167             },
5168             'starterMaxSpawnQuantity_1': {
5169                 'cost': 20000000,
5170                 'description': 'Increases Starter amount of max resources spawned at a time by 1.',
5171                 'type': 'starterMaxSpawnQuantity',
5172                 'amount': 1,
5173             },
5174             'starterMaxSpawnQuantity_2': {
5175                 'cost': 200000000,
5176                 'description': 'Increases Starter amount of max resources spawned at a time by 1.',
5177                 'type': 'starterMaxSpawnQuantity',
5178                 'amount': 1,
5179             },
5180         }
5181 
5182 
5183 class imageLib:
5184     def __init__(self):
5185         self.lib = {
5186             'Key Lock': {
5187                 'image': QtGui.QPixmap('images/Key Lock.gif').scaled(40, 40),
5188             },
5189             'Wall': {
5190                 'image': QtGui.QPixmap('images/Wall.gif'),
5191             }
5192         }
5193