#! usr/bin/env
import re, random, math, itertools
# Custom imports
import GlobalConstants as GC
import configuration as cf
import CustomObjects, MenuFunctions, SaveLoad, Image_Modification, StatusObject, Counters, LevelUp
import Interaction, ItemMethods, WorldMap, Utility, UnitObject, Engine, Banner

import logging
logger = logging.getLogger(__name__)

# === GET INFO FOR DIALOGUE SCENE ==================================================
class Dialogue_Scene(object):
    def __init__(self, scene, optionalunit=None, optionalunit2=None, tile_pos=None, event_flag=True, if_flag=False):
        self.scene = scene
        self.scene_lines = []
        with open(scene, 'r') as scenefp: # Open this scene's file database
            for line in scenefp: # Get lines
                self.scene_lines.append(line)
        if cf.OPTIONS['debug']: 
            if not self.count_if_statements():
                logger.error('ERROR: Incorrect number of if and end statements! %s', scene)

        # Background sprite
        self.background = None
        self.foreground = None
        # Dictionary of scene sprites
        self.unit_sprites = {}
        # Dialogs
        self.dialog = []
        
        # Optional unit
        self.optionalunit = optionalunit
        if self.optionalunit and isinstance(self.optionalunit, UnitObject.UnitObject):
            # logger.debug('locking %s', self.optionalunit)
            self.optionalunit.lock_active()
        self.optionalunit2 = optionalunit2
        if self.optionalunit2 and isinstance(self.optionalunit2, UnitObject.UnitObject):
            # logger.debug('locking %s', self.optionalunit2)
            self.optionalunit2.lock_active()
        # Tile Pos -- What tile is being acted on, if any (applied in unlock, switch, village, destroy, search, etc. scripts)
        self.tile_pos = tile_pos
        # Next Position -- Saves a position
        self.next_position = None
        # Whether we are processing
        self.processingflag = 1
        # Whether we are done
        self.done = False
        # Whether we are waiting for user input
        self.waiting = False

        # For transition
        self.transition = 0 # No transition
        self.transition_transparency = 0 # 0 is transparent
        self.transition_last_update = Engine.get_time()

        self.scene_lines_index = 0 # Keeps track of where we are in the scene
        self.if_stack = [] # Keeps track of how many ifs we've encountered while searching for
        # the bad ifs 'end'.
        self.parse_stack = [] # Keeps track of whether we've encountered a truth this level or not

        self.current_state = "Processing" # Other options are "Waiting", "Transitioning"
        self.last_state = None
        self.do_skip = False
        self.battle_save_flag = False # Whether to enter the battle save state after this scene has completed
        self.reset_state_flag = False # Whether to reset state to free state after this scene has completed
        self.reset_boundary_manager = False # Whether to reset the boundary manager after this scene has completed. Set to true when tiles are changed

        # Handle waiting
        self.last_wait_update = Engine.get_time()
        self.waittime = 0

        # Handles skipping
        self.skippable_commands = {'s', 'u', 'qu', 't', 'c', 'wait',
                                   'wm_move_sprite', 'map_pan', 'set_expression',
                                   'credits', 'endings', 'start_move'}

        # Handles unit face priority
        self.priority_counter = 1

        # Does this count as an event
        self.event_flag = event_flag
        # Assumes all "if" statements evaluate to True?
        self.if_flag = if_flag

    def serialize(self):
        return self.scene

    def count_if_statements(self):
        if_count = 0
        for line in self.scene_lines:
            new_line = line.strip()
            if new_line.startswith('if;'):
                if_count += 1
            elif new_line.startswith('end'):
                if_count -= 1
        return not bool(if_count) # Should be 0.

    def skip(self):
        self.do_skip = True
        if not self.current_state == "Paused":
            self.current_state = "Processing"
        # Reset transition
        self.transition = 0
        self.transition_transparency = 0

    def read_script(self, gameStateObj=None, metaDataObj=None):
        while(self.scene_lines_index < len(self.scene_lines) and self.current_state == "Processing"):
            line = self.scene_lines[self.scene_lines_index]
            # Doing some parsing of the lines
            if len(line) <= 0:
                self.scene_lines_index += 1
                continue

            line = line.strip()
            if line.startswith('#'):
                self.scene_lines_index += 1
                continue

            line = line.split(';')
            # logger.debug(line)
            if (self.if_flag or self.handle_if(line, gameStateObj, metaDataObj)) and (not self.do_skip or not (line[0] in self.skippable_commands)):
                self.parse_line(line, gameStateObj, metaDataObj)
            self.scene_lines_index += 1

    def handle_if(self, line, gameStateObj, metaDataObj):
        # if cf.OPTIONS['debug']: print(line[0], self.if_stack)
        # === CONDITIONALS PARSING
        if line[0] == 'if':
            if not self.if_stack or self.if_stack[-1]:
                truth = eval(line[1])
                self.if_stack.append(truth)
                self.parse_stack.append(truth) # Whether we've encountered a truth this level
            else:
                self.if_stack.append(False)
                self.parse_stack.append(True)
            return False
        elif line[0] == 'elif':
            if not self.if_stack:
                logger.error("Syntax Error somewhere in script. 'elif' needs to be after if statement.")
                return # Impossible. Must have at least parsed an if before hitting an elif
            # If we haven't encountered a truth yet
            if not self.parse_stack[-1]: # if not self.if_stack[-1] and (len(self.if_stack) == 1 or all(t_value for t_value in self.if_stack[:-1])):
                truth = eval(line[1])
                self.if_stack[-1] = truth
                self.parse_stack[-1] = truth
            else:
                self.if_stack[-1] = False
            return False
        elif line[0] == 'else':
            if not self.if_stack:
                logger.error("Syntax Error somewhere in script. 'else' needs to be after if statement.")
                return # Impossible. Must have at least parsed an if before hitting an else
            # If the most recent is False but the rest below are non-existent or true
            if not self.parse_stack[-1]: # not self.if_stack[-1] and (len(self.if_stack) == 1 or all(t_value for t_value in self.if_stack[:-1])):
                self.if_stack[-1] = True
                self.parse_stack[-1] = True
            else:
                self.if_stack[-1] = False # Parse stack stays the same
            return False
        elif line[0] == 'end':
            self.if_stack.pop()
            self.parse_stack.pop()
            return False

        if self.if_stack and not self.if_stack[-1]:
            return False
        return True

    def update(self, gameStateObj=None, metaDataObj=None):
        current_time = Engine.get_time()
        if self.current_state != self.last_state:
            self.last_state = self.current_state
            logger.debug('Dialogue Current State: %s', self.current_state)

        if self.current_state == "Waiting":
            if current_time > self.last_wait_update + self.waittime:
                self.current_state = "Processing" # Done waiting. Head back to processing
            else:
                return # Keep waiting. Do nothing

        elif self.current_state == "Processing":
            self.reset_unit_sprites() # Stop talking when grabbing data - probably only affected at end of file
            # parse sceneLines
            self.read_script(gameStateObj, metaDataObj)
            if self.scene_lines_index >= len(self.scene_lines):
                self.end()

        elif self.current_state == "Transitioning":
            if self.transition == 1: # Increasing
                self.transition_transparency = current_time - self.transition_last_update
            elif self.transition == 2:
                self.transition_transparency = 255 - (current_time - self.transition_last_update)
            elif self.transition == 3:
                self.transition_transparency = (current_time - self.transition_last_update)/2
            elif self.transition == 4:
                self.transition_transparency = 255 - (current_time - self.transition_last_update)/2
            if self.transition_transparency > 255 + 5*GC.FRAMERATE or self.transition_transparency < 0: # I want 5 extra frames in black
                self.current_state = "Processing"
                if self.scene_lines_index >= len(self.scene_lines): # Check if we're done
                    self.end()
            self.transition_transparency = Utility.clamp(self.transition_transparency, 0, 255)

        elif self.current_state == "Displaying": # Don't dialog while transitioning or processing
            if self.dialog: # Only update dialog if it exists, could also just be waiting or not even in this state
                self.dialog[-1].update() # This runs the character generation...
                if self.dialog[-1].done and not self.dialog[-1].waiting:
                    self.current_state = "Processing"

        elif self.current_state == "Paused":
            pass

        if self.dialog and (self.dialog[-1].waiting or self.dialog[-1].done):
            self.reset_unit_sprites()

    def end(self):
        self.done = True
        if self.optionalunit and isinstance(self.optionalunit, UnitObject.UnitObject):
            # logger.debug('Unlocking %s', self.optionalunit)
            self.optionalunit.unlock_active()
        if self.optionalunit2 and isinstance(self.optionalunit2, UnitObject.UnitObject):
            # logger.debug('Unlocking %s', self.optionalunit2)
            self.optionalunit2.unlock_active()
        return

    def parse_line(self, line, gameStateObj=None, metaDataObj=None):
        if line[0] == '':
            return
        # time = Engine.get_true_time()
        logger.info('Script line to parse: %s', line)
        # === SKIPPING DIALOGUE
        # End skip
        if line[0] == 'end_skip':
            self.do_skip = False

        # === PAUSE
        elif line[0] == 'wait':
            self.waittime = int(line[1])
            self.last_wait_update = Engine.get_time()
            self.current_state = "Waiting"

        # === BACKGROUND
        # Change the background
        elif line[0] == 'b':
            if 'map' in line:
                self.background = WorldMap.WorldMapBackground(GC.IMAGESDICT[line[1]], labels=False)
            elif 'advanced' in line:
                self.background = WorldMap.WorldMapBackground(GC.IMAGESDICT[line[1]])
            else:
                self.background = MenuFunctions.StaticBackground(GC.IMAGESDICT[line[1]], fade=False)
        # Remove the background
        elif line[0] == 'remove_background':
            self.background = None

        # === FOREGROUND
        # Change the foreground
        elif line[0] == 'foreground':
            self.foreground = MenuFunctions.StaticBackground(GC.IMAGESDICT[line[1]], fade=False)
        elif line[0] == 'remove_foreground':
            self.foreground = None

        # === WORLD MAP
        elif line[0] == 'wm_move':
            new_position = self.parse_pos(line[1])
            self.background.move(new_position)
        elif line[0] == 'wm_qmove':
            new_position = self.parse_pos(line[1])
            self.background.quick_move(new_position)
        elif line[0] == 'wm_load_sprite' or line[0] == 'wm_load' or line[0] == 'wm_add':
            starting_position = self.parse_pos(line[2])
            if line[0] == 'wm_add':
                starting_position = (starting_position[0]*16, starting_position[1]*16)
            if 'custom' in line[1]:
                klass, gender, team = line[3:6]
            else:
                for unit in gameStateObj.allunits:
                    if unit.name == line[1]:
                        klass = unit.klass
                        gender = 'M' if unit.gender < 5 else 'F'
                        team = unit.team
                        break
                else:
                    logger.warning("Couldn't find unit matching line: %s", line)
                    return
            self.background.add_sprite(line[1], klass, gender, team, starting_position)
        elif line[0] == 'wm_remove_sprite' or line[0] == 'wm_remove':
            self.background.remove_sprite(line[1])
        elif line[0] == 'wm_move_sprite':
            new_position = self.parse_pos(line[2])
            self.background.move_sprite(line[1], new_position)
        elif line[0] == 'wm_label':
            name = line[1]
            position = self.parse_pos(line[2])
            self.background.add_label(name, position)
        elif line[0] == 'wm_label_clear':
            self.background.clear_labels()
        elif line[0] == 'wm_highlight':
            name = line[1]
            self.background.add_highlight(GC.IMAGESDICT['Highlight' + name])
        elif line[0] == 'wm_highlight_clear':
            self.background.clear_highlights()
        elif line[0] == 'wm_cursor':
            pos = self.parse_pos(line[1])
            self.background.create_cursor(pos)
        elif line[0] == 'wm_remove_cursor':
            self.background.remove_cursor()

        # === UNIT SPRITE
        # Add a unit to the scene
        elif line[0] == 'u':
            # This is a complicated method of parsing unit lines using 'u' as delimeter
            spl = []
            w = 'u'
            for x, y in itertools.groupby(line, lambda z: z == w):
                if x:
                    spl.append([])
                spl[-1].extend(y)

            for sub_command in spl:
                if self.add_unit_sprite(sub_command, metaDataObj, transition=True):
                    # Force wait after unit sprite is drawn to allow time to transition.
                    self.waittime = 250
                    self.last_wait_update = Engine.get_time()
                    self.current_state = "Waiting"
        # Add a unit to the scene without transition
        elif line[0] == 'qu':
            self.add_unit_sprite(line, metaDataObj, transition=False)
        # Change a unit's expression
        elif line[0] == 'set_expression':
            self.unit_sprites[line[1]].expression = line[2]
        # Remove a unit from the scene
        elif line[0] == 'r':
            for name in line[1:]:
                unit_name = self.optionalunit.name if name == '{unit}' else name
                if unit_name in self.unit_sprites:
                    self.unit_sprites[unit_name].remove()
                    # Force wait after unit sprite is drawn to allow time to transition.
                    self.waittime = 250
                    self.last_wait_update = Engine.get_time()
                    self.current_state = "Waiting"
        elif line[0] == 'qr': # No transition plox
            for name in line[1:]:
                unit_name = self.optionalunit.name if name == '{unit}' else name
                if unit_name in self.unit_sprites:
                    self.unit_sprites.pop(unit_name)
        # Change a units position
        elif line[0] == 'c':
            name = self.optionalunit.name if line[1] == '{unit}' else line[1]
            self.unit_sprites.pop(name) # Remove line
            self.add_unit_sprite(line, metaDataObj, transition=False)
        # Change the priority of a unit
        elif line[0] == 'change_priority':
            name = self.optionalunit.name if line[1] == '{unit}' else line[1]
            if name in self.unit_sprites:
                self.unit_sprites[name].priority = int(line[2])
        # Move a unit sprite
        elif line[0] == 'move_sprite':
            name = self.optionalunit.name if line[1] == '{unit}' else line[1]
            if name in self.unit_sprites:
                new_position = self.parse_pos(line[2])
                self.unit_sprites[name].move(new_position)
                # Force wait after unit sprite is moved to allow time to transition
                if 'force_hold' in line:
                    self.waittime = abs(new_position[0]/self.unit_sprites[name].unit_speed*self.unit_sprites[name].update_time)+200
                    self.last_wait_update = Engine.get_time()
                    self.current_state = "Waiting"
        # Mirror the unit sprite
        elif line[0] == 'mirror':
            name = self.optionalunit.name if line[1] == '{unit}' else line[1]
            if name in self.unit_sprites:
                self.unit_sprites[name].mirror = not self.unit_sprites[name].mirror
        # Bop the unit sprite up and down
        elif line[0] == 'bop':
            name = self.optionalunit.name if line[1] == '{unit}' else line[1]
            if name in self.unit_sprites:
                self.unit_sprites[name].bop()

        # === MUSIC
        # Fade in this musical accompanienment
        elif line[0] == 'm':
            if line[1] in GC.MUSICDICT:
                logger.debug('Fade in %s', line[1])
                Engine.music_thread.fade_in(GC.MUSICDICT[line[1]])
            else:
                logger.warning("Couldn't find music matching %s", line[1])
        elif line[0] == 'mf':
            logger.debug('Fade out music')
            Engine.music_thread.fade_back()

        # === HANDLE ITEMS
        # Give the optional unit an item or give the unit named in the line the item
        elif line[0] == 'give_item' or line[0] == 'add_item':
            # Find receiver
            if line[1] == '{unit}' and self.optionalunit:
                receiver = self.optionalunit
            elif line[1] == 'Convoy':
                receiver = None
            else:
                receiver = gameStateObj.get_unit_from_name(line[1])
            # Append item to list of units items
            if line[2] != "0":
                item = ItemMethods.itemparser(line[2])[0]
                self.add_item(receiver, item, gameStateObj, len(line) <= 3)
            elif line[2] == "0":
                gameStateObj.banners.append(Banner.foundNothingBanner(receiver))
                gameStateObj.stateMachine.changeState('itemgain')
                self.current_state = "Paused"

        # Has the unit equip an item in their inventory if and only if that item is in their inventory by name
        elif line[0] == 'equip_item':
            if line[1] == '{unit}':
                name = self.optionalunit.name
            else:
                name = line[1]
            for unit in gameStateObj.allunits:
                if unit.name == name:
                    for item in unit.items:
                        if item.name == line[2]:
                            unit.equip(item)
                            break
                break
        # Give the gameStateObj GC.COLORDICT['gold']!
        elif line[0] == 'gold':
            gameStateObj.counters['money'] += int(line[1])
            gameStateObj.banners.append(Banner.acquiredGoldBanner(int(line[1])))
            gameStateObj.stateMachine.changeState('itemgain')
            self.current_state = "Paused"

        elif line[0] == 'remove_item':
            if line[1] == '{unit}':
                item = [item for item in self.optionalunit.items if item.name == line[2] or item.id == line[2]][0]
                self.optionalunit.remove_item(item)
            else:
                for unit in gameStateObj.allunits:
                    if unit.name == line[1]:
                        item = [item for item in self.optionalunit.items if item.name == line[2] or item.id == line[2]][0]
                        unit.remove_item(item)
                        break

        # Add a skill/status to a unit
        elif line[0] == 'give_skill':
            skill = StatusObject.statusparser(line[2])
            if line[1] == '{unit}' and self.optionalunit:
                StatusObject.HandleStatusAddition(skill, self.optionalunit, gameStateObj)
                if 'no_display' not in line:
                    gameStateObj.banners.append(Banner.gainedSkillBanner(self.optionalunit, skill))
                    gameStateObj.stateMachine.changeState('itemgain')
                    self.current_state = "Paused"
            else:
                for unit in gameStateObj.allunits: # Find the unit this is supposed to go to
                    if unit.name == line[1]:
                        # print(unit.name)
                        StatusObject.HandleStatusAddition(skill, unit, gameStateObj)
                        if 'no_display' not in line:
                            gameStateObj.banners.append(Banner.gainedSkillBanner(unit, skill))
                            gameStateObj.stateMachine.changeState('itemgain')
                            self.current_state = "Paused"
                        break

        # Give exp to a unit
        elif line[0] == 'exp_gain':
            exp = int(line[2])
            c_unit = None
            if line[1] == '{unit}' and self.optionalunit:
                c_unit = self.optionalunit
            else:
                for unit in gameStateObj.allunits:
                    if unit.name == line[1]:
                        c_unit = unit
            gameStateObj.levelUpScreen.append(LevelUp.levelUpScreen(gameStateObj, unit=c_unit, exp=exp))
            gameStateObj.stateMachine.changeState('expgain')
            self.current_state = "Paused"

        # === HANDLES UNITS ON MAP
        elif line[0] == 'add_unit':
            # Read input
            which_unit = line[1]
            to_which_position = line[2] if len(line) > 2 else None
            transition = line[3] if (len(line) > 3 and line[3]) else 'fade'
            placement = line[4] if (len(line) > 4 and line[4]) else 'give_up'
            order = True if len(line) > 5 else False 
            self.add_unit(gameStateObj, metaDataObj, which_unit, to_which_position, transition, placement, shuffle=not order)
        elif line[0] == 'create_unit':
            # Read input
            which_unit = line[1]
            level = eval(line[2])
            to_which_position = line[3] if len(line) > 3 else None
            transition = line[4] if (len(line) > 4 and line[4]) else 'fade'
            placement = line[5] if (len(line) > 5 and line[5]) else 'give_up'
            order = True if len(line) > 6 else False 
            self.add_unit(gameStateObj, metaDataObj, which_unit, to_which_position, transition, placement, shuffle=not order, create=level)
        elif line[0] == 'move_unit':
            # Read input
            which_unit = line[1]
            to_which_position = line[2]
            transition = line[3] if (len(line) > 3 and line[3]) else 'normal'
            placement = line[4] if (len(line) > 4 and line[4]) else 'give_up'
            order = True if len(line) > 5 else False
            self.move_unit(gameStateObj, metaDataObj, which_unit, to_which_position, transition, placement, shuffle=not order)
        elif line[0] == 'start_move':
            self.current_state = "Paused"
            gameStateObj.stateMachine.changeState('movement')
        elif line[0] == 'interact_unit':
            # Read input
            attacker = line[1]
            defender = line[2]
            event_combat = False if len(line) > 3 else True
            self.interact_unit(gameStateObj, attacker, defender, event_combat)
        elif line[0] == 'remove_unit' or line[0] == 'kill_unit':
            # Read input
            which_unit = line[1]
            transition = line[2] if (len(line) > 2 and line[2]) else 'fade'
            event = (line[0] == 'remove_unit')
            self.remove_unit(gameStateObj, which_unit, transition, event=event)
        elif line[0] == 'set_next_position':
            to_which_position = line[1]
            placement = line[2] if len(line) > 2 else 'give_up'
            order = True if len(line) > 3 else False
            self.find_next_position(gameStateObj, to_which_position, placement, shuffle=not order)

        # === HANDLE CURSOR
        elif line[0] in ['set_cursor', 'move_cursor']:
            if line[1].startswith('o') and "," in line[1]:
                coord = self.parse_pos(line[1][1:])
                if gameStateObj.map.custom_origin:
                    coord = coord[0] + gameStateObj.map.custom_origin[0], coord[1] + gameStateObj.map.custom_origin[1]
            elif "," in line[1]: # If is a coordinate
                coord = self.parse_pos(line[1])
            elif line[1] == 'next' and self.next_position:
                coord = self.next_position
            elif line[1] == '{unit}' and self.optionalunit:
                    coord = self.optionalunit.position
            else:
                for unit in gameStateObj.allunits:
                    if (unit.name == line[1] or unit.event_id == line[1]) and unit.position:
                        coord = unit.position
                        break
                else:
                    logger.error("Couldn't find unit %s", line[1])
                    return
            gameStateObj.cursor.setPosition(coord, gameStateObj)
            if (line[0] == 'move_cursor' or 'force_hold' in line) and not self.do_skip:
                gameStateObj.stateMachine.changeState('move_camera')
                self.current_state = "Paused"
        # Display Cursor 1 is yes, 0 is no
        elif line[0] == 'disp_cursor' or line[0] == 'display_cursor':
            choice_flag = int(line[1])
            if choice_flag:
                gameStateObj.cursor.drawState = 1
            else:
                gameStateObj.cursor.drawState = 0
        elif line[0] == 'set_camera':
            pos1 = self.parse_pos(line[1])
            pos2 = self.parse_pos(line[2])
            gameStateObj.cameraOffset.center2(pos1, pos2)
            if len(line) > 3 and line[3] == 'force':  # Force
                pass
            else:
                gameStateObj.stateMachine.changeState('move_camera')      
                self.current_state = "Paused"
        elif line[0] == 'tutorial_mode':
            if line[1] == '0':
                gameStateObj.tutorial_mode_off()
            else:
                gameStateObj.tutorial_mode = line[1]
        elif line[0] == 'fake_cursor':
            coords = line[1:]
            for text_coord in coords:
                coord = [int(num) for num in text_coord.split(',')]
                gameStateObj.fake_cursors.append(CustomObjects.Cursor('Cursor', coord, fake=True))
        elif line[0] == 'remove_fake_cursors':
            gameStateObj.remove_fake_cursors()
        elif line[0] == 'set_camera_pan':
            choice_flag = int(line[1])
            if choice_flag:
                gameStateObj.cameraOffset.pan_flag = True
            else:
                gameStateObj.cameraOffset.pan_flag = False
        elif line[0] == 'map_pan':
            gameStateObj.cameraOffset.pan_flag = True
            gameStateObj.cameraOffset.map_pan(gameStateObj.map, gameStateObj.cursor.position)
            gameStateObj.stateMachine.changeState('move_camera')
            self.current_state = 'Paused'

        # === HANDLE OBJECTIVE
        elif line[0] == 'change_objective_display_name':
            gameStateObj.objective.display_name_string = line[1]
        elif line[0] == 'change_objective_win_condition':
            gameStateObj.objective.win_condition_string = line[1]
        elif line[0] == 'change_objective_loss_condition':
            gameStateObj.objective.loss_condition_string = line[1]
        elif line[0] == 'add_minimum_number_banner':
            gameStateObj.banners.append(Banner.tooFewUnitsBanner())
            gameStateObj.stateMachine.changeState('itemgain')
            self.current_state = "Paused"
        elif line[0] == 'switch_pulled_banner':
            gameStateObj.banners.append(Banner.switchPulledBanner())
            gameStateObj.stateMachine.changeState('itemgain')
            self.current_state = "Paused"
        elif line[0] == 'lose_game':
            gameStateObj.statedict['levelIsComplete'] = 'loss'
        elif line[0] == 'win_game':
            gameStateObj.statedict['levelIsComplete'] = 'win'
        elif line[0] == 'skip_outro':
            gameStateObj.statedict['outroScriptDone'] = True

        elif line[0] == 'battle_save':
            # Using a flag instead of just going to battle save state because if I save while
            # there's a dialogue state on the stack, the dialogue has a surface which crashes the save
            # This problem might not exist anymore, but...
            self.battle_save_flag = True
        elif line[0] == 'reset_state':
            self.reset_state_flag = True

        # === HANDLE TILE CHANGES -- These get put in the command list
        elif line[0] == 'set_custom_origin':
            if len(line) > 1:
                gameStateObj.map.custom_origin = self.parse_pos(line[1])
                line = [line[0], self.parse_pos(line[1])]
            else:
                gameStateObj.map.custom_origin = self.tile_pos
                line.append(self.tile_pos)
            gameStateObj.map.command_list.append(line)
        # Change tile sprites. - command, pos, tile_sprite, size, transition
        elif line[0] == 'change_tile_sprite' or line[0] == 'change_sprite':
            # Add default transition
            if len(line) < 4:
                line.append('fade')
            gameStateObj.map.change_sprite(line)
            # Ways of destroying. Defaults to instantaneous in command list
            if 'fade' in line:
                line.remove('fade')
            elif 'destroy' in line:
                line.remove('destroy')
            gameStateObj.map.command_list.append(line)
        elif line[0] == 'layer_tile_sprite':
            gameStateObj.map.layer_tile_sprite(line)
            gameStateObj.map.command_list.append(line)
        elif line[0] == 'show_layer':
            if len(line) < 3:
                line.append('fade')
            gameStateObj.map.show_layer(line)
            if 'fade' in line:
                line.remove('fade')
            elif 'destroy' in line:
                line.remove('destroy')
            gameStateObj.map.command_list.append(line)
        elif line[0] == 'hide_layer':
            if len(line) < 3:
                line.append('fade')
            gameStateObj.map.hide_layer(line)
            if 'fade' in line:
                line.remove('fade')
            elif 'destroy' in line:
                line.remove('destroy')
            gameStateObj.map.command_list.append(line)
        elif line[0] == 'clear_layer':
            gameStateObj.map.clear_layer(line[1])
            gameStateObj.map.command_list.append(line)
        # Change one tile
        elif line[0] == 'replace_tile':
            coords = gameStateObj.map.replace_tile(line, gameStateObj.grid_manager)
            gameStateObj.map.command_list.append(line)
            self.reset_boundary_manager = True
            # gameStateObj.boundary_manager.reset(gameStateObj)
        # Change area of tile (must include pic instead of id)
        elif line[0] == 'area_replace_tile':
            coord, size = gameStateObj.map.mass_replace_tile(line, gameStateObj.grid_manager)
            gameStateObj.map.command_list.append(line)
            self.reset_boundary_manager = True
            # width, height = size
            # gameStateObj.boundary_manager.reset(gameStateObj)
        # Change one tile's information
        elif line[0] == 'set_tile_info':
            gameStateObj.map.set_tile_info(line)
            gameStateObj.map.command_list.append(line)
        # Changing whole map tile data!
        elif line[0] == 'load_new_map_tiles':
            gameStateObj.map.load_new_map_tiles(line, gameStateObj.counters['level'])
            gameStateObj.map.command_list.append(line)
            gameStateObj.boundary_manager.reset(gameStateObj)
        # Changing whole map's sprites!
        elif line[0] == 'load_new_map_sprite':
            gameStateObj.map.load_new_map_sprite(line, gameStateObj.counters['level'])
            gameStateObj.map.command_list.append(line)
        # Reset whole map's tile_info!
        elif line[0] == 'reset_map_tile_info':
            gameStateObj.map.reset_tile_info()
            gameStateObj.map.command_list.append(line)
        # Add weather
        elif line[0] == 'add_weather':
            gameStateObj.map.add_weather(line[1])
            gameStateObj.map.command_list.append(line)
        # Remove weather
        elif line[0] == 'remove_weather':
            gameStateObj.map.remove_weather(line[1])
            gameStateObj.map.command_list.append(line)
        # Add global status
        elif line[0] == 'add_global_status':
            gameStateObj.map.add_status(line[1])
            gameStateObj.map.command_list.append(line)
        # Remove global status
        elif line[0] == 'remove_global_status':
            gameStateObj.map.remove_status(line[1])
            gameStateObj.map.command_list.append(line)
        # Remove global status
        # Clear command list
        elif line[0] == 'clear_command_list':
            if len(line) > 1:
                gameStateObj.map.command_list = [command for command in gameStateObj.map.command_list if command[0] != line[1]]
            else:
                gameStateObj.map.command_list = []
        elif line[0] == 'clear_command_list_except':
            gameStateObj.map.command_list = [command for command in gameStateObj.map.command_list if command[0] == line[1]]

        # === CLEANUP
        elif line[0] == 'arrange_formation':
            if len(line) > 1:  # force arrange
                player_units = [unit for unit in gameStateObj.allunits if unit.team == 'player' and
                                not unit.dead]
                formation_spots = [pos for pos, value in gameStateObj.map.tile_info_dict.iteritems()
                                   if 'Formation' in value]
            else:
                player_units = [unit for unit in gameStateObj.allunits if unit.team == 'player' and
                                not unit.dead and not unit.position]
                formation_spots = [pos for pos, value in gameStateObj.map.tile_info_dict.iteritems()
                                   if 'Formation' in value and not gameStateObj.grid_manager.get_unit_node(pos)]
            for index, unit in enumerate(player_units[:len(formation_spots)]):
                if len(line) > 1:
                    unit.leave(gameStateObj)
                    unit.remove_from_map(gameStateObj)
                unit.position = formation_spots[index]
                # print(unit.name, unit.position)
                unit.place_on_map(gameStateObj)
                unit.arrive(gameStateObj)
        elif line[0] == 'reset_units':
            for unit in gameStateObj.allunits:
                unit.reset()
        elif line[0] == 'reset_unit':
            if line[1] == '{unit}':
                self.optionalunit.reset()
            else:
                for unit in gameStateObj.allunits:
                    if unit.id == line[1] or unit.event_id == line[1] or unit.name == line[1] or unit.team == line[1]:
                        unit.reset()
        elif line[0] == 'remove_enemies':
            if len(line) > 1:
                exception = line[1]
            else:
                exception = None
            # Remove enemies
            gameStateObj.allunits = [unit for unit in gameStateObj.allunits if unit.team == 'player' or unit.name == exception]
        elif line[0] == 'kill_all':
            if len(line) > 1:
                call_out = line[1]
            else:
                call_out = None
            for unit in gameStateObj.allunits:
                if unit.position and unit.team == call_out:
                    unit.isDying = True
                    gameStateObj.stateMachine.changeState('dying')
        # === GAME CONSTANTS
        # should be remembered for map
        elif line[0] == 'trigger_event':
            gameStateObj.event_triggers.append(line[1])
        elif line[0] == 'resolve_event':
            if line[1] in gameStateObj.event_triggers:
                gameStateObj.event_triggers.remove(line[1])
        # should be remembered for all game
        elif line[0] == 'add_game_constant' or line[0] == 'set_game_constant':
            gameStateObj.game_constants.append(line[1])
        elif line[0] == 'unlock_lore':
            gameStateObj.unlocked_lore.append(line[1])
        elif line[0] == 'remove_lore':
            if line[1] in gameStateObj.unlocked_lore:
                del gameStateObj.unlocked_lore[line[1]]
        elif line[0] == 'set_counter':
            gameStateObj.counters[line[1]] = 0
            if len(line) > 2:
                gameStateObj.counters[line[1]] = int(eval(line[2]))
        elif line[0] == 'inc_counter':
            if line[1] in gameStateObj.counters:
                if len(line) > 2:
                    gameStateObj.counters[line[1]] += int(eval(line[2]))
                else:
                    gameStateObj.counters[line[1]] += 1
            else:
                logger.warning('No counter named %s found in gameStateObj.counters! Did you remember to set it?', line[1])
        elif line[0] == 'metaDataObj':
            gameStateObj.metaDataObj_changes.append(line)
            metaDataObj[line[1]] = line[2]
            if line[1].endswith('Music'):
                metaDataObj[line[1]] = GC.MUSICDICT[line[2]]
        elif line[0] == 'event_flag':
            self.event_flag = bool(int(line[1]))
        elif line[0] == 'add_to_market':
            gameStateObj.market_items.add(line[1])
        elif line[0] == 'remove_from_market':
            gameStateObj.market_items.discard(line[1])
            
        # === TRANSITIONS
        elif line[0] == 't': # Handle transition
            if line[1] == '1':
                self.transition = 1
                self.transition_transparency = 0 # Increasing
            elif line[1] == '2':
                self.transition = 2
                self.transition_transparency = 255 # Decreasing
            elif line[1] == '3':
                self.transition = 3
                self.transition_transparency = 0
            elif line[1] == '4':
                self.transition = 4
                self.transition_transparency = 255
            self.transition_last_update = Engine.get_time()
            self.current_state = "Transitioning"
        
        # === CHANGING UNITS
        elif line[0] == 'convert':
            unit_specifier = self.get_id(line[1])
            for unit in gameStateObj.allunits:
                if unit.name == unit_specifier or unit.event_id == unit_specifier or unit.position == unit_specifier:
                    unit.changeTeams(line[2], gameStateObj)
        elif line[0] == 'change_class':
            unit_specifier = self.get_id(line[1])
            for unit in gameStateObj.allunits:
                if unit.name == unit_specifier or unit.event_id == unit_specifier or unit.position == unit_specifier:
                    unit.changeClass(line[2], gameStateObj)
        elif line[0] == 'change_ai':
            unit_specifier = self.get_id(line[1])
            for unit in gameStateObj.allunits:
                if unit.name == unit_specifier or unit.event_id == unit_specifier or unit.position == unit_specifier:
                    unit.ai_descriptor = line[2]
                    unit.get_ai(line[2])
        elif line[0] == 'add_tag':
            unit_specifier = self.get_id(line[1])
            for unit in gameStateObj.allunits:
                if unit.name == unit_specifier or unit.event_id == unit_specifier or unit.position == unit_specifier:
                    unit.tags.add(line[2])

        # === HANDLE TALKING
        elif line[0] == 'add_talk':
            # Add to dictionary
            gameStateObj.talk_options.append((line[1], line[2]))
        elif line[0] == 'remove_talk':
            if (line[1], line[2]) in gameStateObj.talk_options:
                gameStateObj.talk_options.remove((line[1], line[2]))
        elif line[0] == 'set_base_convo':
            # Add to dictionary
            gameStateObj.base_conversations[line[1]] = True
        elif line[0] == 'remove_base_convo':
            if line[1] in gameStateObj.base_conversations:
                del gameStateObj.base_conversations[line[1]]
        elif line[0] == 'grey_base_convo':
            if line[1] in gameStateObj.base_conversations:
                gameStateObj.base_conversations[line[1]] = False
            
        # === DIALOGUE BOX
        # Add line of text          
        elif line[0] == 's':
            self.evaluate_evals(line, gameStateObj)
            self.add_dialog(line)
        # === CREDITS BOX
        elif line[0] == 'credits':
            self.add_credits(line)
        # === ENDINGS BOX
        elif line[0] == 'endings':
            self.add_ending(line, gameStateObj)
        # === Pop dialog off top
        elif line[0] == 'pop_dialog':
            if self.dialog:
                self.dialog.pop()

        # === Show Victory Screen
        elif line[0] == 'victory_screen':
            gameStateObj.stateMachine.changeState('victory')
            self.current_state = 'Paused'
        # === ROLL CREDITS
        elif line[0] == 'roll_credits':
            gameStateObj.stateMachine.changeState('credits')
            self.current_state = "Paused"
        # === Display Records
        elif line[0] == 'records_display':
            gameStateObj.stateMachine.changeState('base_records')
            self.current_state = "Paused"

        # logger.info('Time taken: %s', Engine.get_true_time() - time)

    def evaluate_evals(self, line, gameStateObj):
        # Evaluate evals
        if '{eval:' in line[2]:
            dialog = line[2][:]
            last_index = None
            add = False
            command = []
            # Traverse backwards so we don't mess up the indexing
            for i, char in reversed(list(enumerate(dialog))):
                if char == '}':
                    last_index = i
                    add = True
                elif char == '{':
                    command = ''.join(command)
                    if command.startswith('eval:'):
                        to_eval = command[5:]
                        result = str(eval(to_eval))
                        line[2] = line[2][:i] + result + line[2][last_index+1:]
                    add = False
                    command = []
                elif add:
                    command.insert(0, char)
        return line[2]

    def add_dialog(self, line):
        speaker = self.optionalunit.name if line[1] == '{unit}' else line[1]
        tail = None
        waiting_cursor = True
        transition = False
        thought_bubble = False
        if 'hint' in line:
            if 'auto' in line:
                position = GC.WINWIDTH/4, GC.WINHEIGHT/4
                size = GC.WINWIDTH/2 + 8, GC.WINHEIGHT/2
            else:
                position = [int(line[3]), int(line[4])]
                size = ((int(line[5]) if line[5] else GC.WINWIDTH/2 + 8), GC.WINHEIGHT/2)
            back_surf = 'Parchment_Window'
            font = 'convo_black'
            num_lines = 4
        elif 'narration' in line:
            if 'auto' in line:
                position = 4, 110
                size = 232, 48
            else:
                position = [int(line[3]), int(line[4])]
                size = int(line[5]) if len(line) > 5 else 144, 48
            back_surf = 'BaseMenuBackground'
            font = 'convo_white'
            num_lines = 2
        elif 'cinematic' in line:
            position = 'center'
            size = GC.WINWIDTH, GC.WINHEIGHT
            back_surf = 'ActualTransparent'
            font = 'chapter_grey'
            num_lines = 5
            waiting_cursor = False
        else:
            owner = self.unit_sprites[speaker] if speaker in self.unit_sprites else None
            # --- Quick fix
            if len(line) == 3:
                # s, speaker, text -- Assume we forgot {w} auto
                line[2] += '{w}'
                line.append('auto')

            if 'auto' in line:
                position, size = self.auto_dialog_box(line[2], owner)
            else:
                position = [int(line[3]), int(line[4])]
                size = int(line[5]) if len(line) > 5 else 144, 48
            if 'noir' in line:
                back_surf = 'NoirMessageWindow'

            else:
                back_surf = 'MessageWindowBackground'
            if 'thought_bubble' in line:
                thought_bubble = True
            if 'noir' in line:
                tail = None
            elif thought_bubble:
                tail = GC.IMAGESDICT['ThoughtWindowTail'] if owner else None
            else:
                tail = GC.IMAGESDICT['MessageWindowTail'] if owner else None
            font = 'convo_black'
            if 'noir' in line:
                font = 'convo_white'
            num_lines = 2
            transition = True
        self.dialog.append(Dialog(line[2], speaker, position, size, font, back_surf, tail, num_lines,
                                  waiting_cursor_flag=waiting_cursor, transition=transition,
                                  unit_sprites=self.unit_sprites, slow_flag=3 if 'slow' in line else 1,
                                  talk=not thought_bubble))

        self.reset_unit_sprites()
        if self.dialog[-1].owner in self.unit_sprites:
            self.unit_sprites[self.dialog[-1].owner].priority = self.priority_counter
            self.priority_counter += 1

        # Stop processing
        self.current_state = "Displaying"

    def auto_dialog_box(self, dialogue, owner):
        position = [8, 104]
        length = GC.WINWIDTH - 8*2
        num_lines = 2
        if owner:
            # Split on breaks and clears
            longest_dialogue_size = 0
            current_dialogue = []
            command = []
            in_command = False
            for character in dialogue:
                if character == '{':
                    in_command = True

                if in_command:
                    command.append(character)
                else:
                    current_dialogue.append(character)

                if character == '}':
                    in_command = False
                    command_text = ''.join(command)
                    if command_text == '{clear}' or command_text == '{br}':
                        current_text = ''.join(current_dialogue)
                        # Now find out how big this needs to be to hold this
                        width = self.determine_width(current_text, num_lines)
                        if width > longest_dialogue_size:
                            longest_dialogue_size = width
                        # Clear current dialogue
                        current_dialogue = []
                    # Clear command
                    command = []
            # And do it at the end
            current_text = ''.join(current_dialogue)
            # Now find out how big this needs to be to hold this
            width = self.determine_width(current_text, num_lines)
            if width > longest_dialogue_size:
                longest_dialogue_size = width

            length = longest_dialogue_size + 8*2
            if owner.position[0] < 0:
                position = [8, 24]
            elif owner.position[0] < 24:
                position = [24, 24]
            elif owner.position[0] > 144:
                position = [GC.WINWIDTH - length - 8, 24]
            elif owner.position[0] > 120:
                position = [GC.WINWIDTH - length - 24, 24]
            elif owner.position[0] >= 24 and owner.position[0] < 56:
                position = [40, 24]
            elif owner.position[0] <= 120 and owner.position[0] > 88:
                position = [min(GC.WINWIDTH - length - 40, 96), 24]
            else:
                position = [120 - length/2, 24]
            # Error checking
            if position[0] < 8:
                position[0] = 8
            if position[0] + length > GC.WINWIDTH - 8:
                length = GC.WINWIDTH - 8 - position[0]
        return position, (length, 48)

    def determine_width(self, text, num_lines):
        for w in range(32, GC.WINWIDTH - 8*5, 8):
            # print('width', w)
            output_lines = MenuFunctions.line_wrap(MenuFunctions.line_chunk(text), w, GC.FONT['convo_black'], test=True)
            if len(output_lines) <= num_lines:
                return w + 16 # This is an extra buffer to account for waiting cursor
        # If we got here, it was too bug
        logger.warning('Text too big for dialog box!')
        # print('Text too big for dialog box!')
        return GC.WINWIDTH - 8*4

    def add_credits(self, line):
        title = line[1]
        text = line[2:]
        if 'wait' in text:
            text.remove('wait')
            wait = True
        else:
            wait = False
        if 'center' in text:
            text.remove('center')
            center = True
        else:
            center = False
        title_font = 'credittitle_white'
        font = 'credit_white'

        new_credits = Credits(title, text, title_font, font, wait, center)
        # Wait a certain amount of time before next one
        self.waittime = new_credits.determine_wait()
        self.last_wait_update = Engine.get_time()
        self.current_state = "Waiting"

        self.dialog.append(new_credits)

    def add_ending(self, line, gameStateObj):
        portrait = line[1]
        title = line[2]
        text = line[3]
        font = 'text_white'

        new_ending = EndingsDisplay(self, portrait, title, text, gameStateObj.statistics, font)

        self.dialog.append(new_ending)

        # Stop processing
        self.current_state = "Displaying"

    def draw(self, surf):
        # Draw if background exists
        if self.background:
            # Blit Background
            self.background.draw(surf)

        # Update unit sprites -- if results in true, delete the unit sprite. -- faciliates 'r' command fade out
        delete = [key for key, unit in self.unit_sprites.iteritems() if unit.update()]
        for key in delete: 
            del self.unit_sprites[key]

        # === SCENE SPRITES ===
        # Blit sprites, sort them by their priority (ascending, so 2 is pasted after and on top of 1)
        sorted_sprites = sorted([unit for key, unit in self.unit_sprites.iteritems()], key=lambda x: x.priority)
        for unit in sorted_sprites:
            unit.draw(surf)

        if self.dialog and (self.current_state == "Displaying" or self.dialog[-1].hold):# Draw text (Don't draw text while transitioning)
            for dialog in reversed(self.dialog):
                dialog.draw(surf, self.unit_sprites)
                if dialog.solo_flag:
                    break

        if self.foreground:
            self.foreground.draw(surf)

        s = GC.IMAGESDICT['BlackBackground'].copy()
        s.fill((0, 0, 0, self.transition_transparency))
        surf.blit(s, (0, 0))

    def add_unit_sprite(self, line, metaDataObj, transition=False):
        name = self.optionalunit.name if line[1] == '{unit}' else line[1]
        if name in self.unit_sprites and not self.unit_sprites[name].remove_flag:
            return False
        hardset_positions = {'OffscreenLeft': -96, 'FarLeft': -24, 'Left': 0, 'MidLeft': 24,
                             'MidRight': 120, 'Right': 144, 'FarRight': 168, 'OffscreenRight': 240}
        if line[2] in hardset_positions:
            position = [hardset_positions[line[2]], 80]
            priority = self.priority_counter
            self.priority_counter += 1
            mirrorflag = True if line[2] in ['OffscreenLeft', 'FarLeft', 'Left', 'MidLeft'] else False
            if 'mirror' in line:
                mirrorflag = not mirrorflag
        else:
            position = [int(line[2]), int(line[3])]
            priority = int(line[4])
            mirrorflag = True if 'mirror' in line else False
        if 'Full_Blink' in line:
            expression = 'Full_Blink'
        elif 'Smiling' in line:
            expression = 'Smiling'
        else:
            expression = 'Normal'
        assert name in metaDataObj['portrait_dict'], "%s not in portrait dictionary. Need to assign blink and mouth positions to pic"%(name)
        blink = metaDataObj['portrait_dict'][name]['blink']
        mouth = metaDataObj['portrait_dict'][name]['mouth']
        self.unit_sprites[name] = UnitPortrait(portrait_name=name, blink_position=blink, mouth_position=mouth, transition=transition,
                                               position=position, priority=priority, mirror=mirrorflag, expression=expression)

        return True

    def reset_unit_sprites(self):
        for key, unit in self.unit_sprites.iteritems():
            unit.stop_talking()

    def dialog_unpause(self):
        # Removes waiting from dialog
        if not self.dialog[-1].done and not self.dialog[-1].is_done():
            if self.dialog[-1].talk and self.dialog[-1].owner in self.unit_sprites:
                self.unit_sprites[self.dialog[-1].owner].talk()
        self.dialog[-1].waiting = False

    def add_unit(self, gameStateObj, metaDataObj, which_unit, new_pos, transition, placement, shuffle=True, create=None):
        # Find unit
        if create:
            unitLine = gameStateObj.prefabs.get(which_unit)
            if not unitLine:
                logger.warning('Could not find %s' in unitLine)
                return
            new_unitLine = unitLine[:]
            new_unitLine.insert(4, create)
            unit = SaveLoad.create_unit(new_unitLine, gameStateObj.allunits, gameStateObj.groups, gameStateObj.allreinforcements, metaDataObj, gameStateObj)
            position = self.parse_pos(unitLine[5])
        else:
            context = gameStateObj.allreinforcements.get(which_unit)
            if not context:
                logger.warning('Could not find %s in allreinforcements', which_unit)
                return
            u_id, position = context
            unit = gameStateObj.get_unit_from_id(u_id)
            if unit.dead:
                logger.warning('Unit %s is dead', u_id)
                return
        if not unit:
            logger.error('Could not find unit %s', which_unit)
            return

        # Determine where the unit will appear
        # If none, then use position in load
        if not new_pos:
            new_pos = [position]
        # Using prev defined reinforcement positions
        elif new_pos.startswith('r'):
            new_pos = self.get_rein_position(new_pos[1:], gameStateObj)
        # Using next position
        elif new_pos == 'next':
            new_pos = [self.next_position]
        # If coord, use coord
        elif ',' in new_pos:
            new_pos = self.get_position(new_pos)
        # If name, then we want to find a point adjacent to that characters position
        else:
            new_char = gameStateObj.get_unit_from_id(new_pos)
            if new_char:
                new_pos = [new_char.position]
            else:
                logger.error('Could not find unit %s', new_pos)
                return

        # Shuffle positions if necessary
        if shuffle:
            random.shuffle(new_pos)

        # Determine which positions I can't move onto
        bad_pos = {bad_unit.position for bad_unit in gameStateObj.allunits if bad_unit.position}

        final_pos = self.get_final_pos(gameStateObj, placement, new_pos, bad_pos)
        if not final_pos:
            logger.warning('Could not add to position %s', new_pos)
            return

        if self.do_skip:
            transition = 'immediate'
        # Now we have final pos
        unit.position = final_pos
        if transition == 'warp':
            unit.sprite.set_transition('warp_in')
            gameStateObj.map.initiate_warp_flowers(final_pos)
        elif transition == 'fade':
            if gameStateObj.map.on_border(final_pos):
                unit.sprite.spriteOffset = [num*GC.TILEWIDTH for num in gameStateObj.map.which_border(final_pos)]
                unit.sprite.set_transition('fake_in')
            else:
                unit.sprite.set_transition('fade_in')
        elif transition == 'immediate':
            pass
        unit.place_on_map(gameStateObj)
        unit.arrive(gameStateObj)

    def move_unit(self, gameStateObj, metaDataObj, which_unit, new_pos, transition, placement, shuffle=True):
        # Find unit
        if which_unit == '{unit}':
            unit = self.optionalunit
        elif ',' in which_unit:
            unit = gameStateObj.get_unit_from_pos(self.parse_pos(which_unit))
        else:
            unit = gameStateObj.get_unit(which_unit)
        if not unit:
            logger.error('Move unit routine could not find unit %s', which_unit)
            return
        if not unit.position:
            logger.error('Unit does not have position! %s %s', which_unit, unit.name)
            print('Unit does not have position! %s %s' %(which_unit, unit.name))
            gameStateObj.display_all_units()
            return

        # Determine available positions to move to
        # Using prev defined reinforcement positions
        if new_pos.startswith('r'):
            new_pos = self.get_rein_position(new_pos[1:], gameStateObj)
        # Using next position
        elif new_pos == 'next':
            new_pos = [self.next_position]
        # Using prev position
        elif new_pos == 'prev_pos':
            new_pos = [unit.previous_position]
        # If coord, use coord
        elif ',' in new_pos:
            new_pos = self.get_position(new_pos)
        # If name, then we want to find a point adjacent to that characters position
        else:
            new_pos = [gameStateObj.get_unit_from_id(new_pos).position]

        # Shuffle positions if necessary
        if shuffle:
            random.shuffle(new_pos)

        # Determine which positions I can't move onto
        bad_pos = {bad_unit.position for bad_unit in gameStateObj.allunits if bad_unit.position}

        final_pos = self.get_final_pos(gameStateObj, placement, new_pos, bad_pos)
        if not final_pos:
            logger.warning('Could not move to position %s', new_pos)
            return

        if self.do_skip:
            transition = 'immediate'
        if transition == 'normal':
            movePath = unit.getPath(gameStateObj, final_pos)
            unit.beginMovement(gameStateObj, movePath)
        elif transition == 'warp':
            unit.sprite.set_transition('warp_move')
            unit.sprite.set_next_position(final_pos)
        elif transition == 'fade':
            unit.sprite.set_transition('fade_move')
            unit.sprite.set_next_position(final_pos)
        elif transition == 'immediate':
            unit.leave(gameStateObj)
            unit.position = final_pos
            unit.arrive(gameStateObj)

    def remove_unit(self, gameStateObj, which_unit, transition, event=True):
        # Find unit
        if which_unit == '{unit}':
            unit = self.optionalunit
        elif ',' in which_unit:
            unit = gameStateObj.get_unit_from_pos(self.parse_pos(which_unit))
        else:
            unit = gameStateObj.get_unit(which_unit)
        if not unit:
            logger.error('Remove unit routine could not find unit %s', which_unit)
            return
        if not unit.position:
            logger.warning('Remove unit routine - No position')
            return

        if self.do_skip:
            transition = 'immediate'
        if transition == 'warp':
            unit.sprite.set_transition('warp_out')
        elif transition == 'fade':
            if event and gameStateObj.map.on_border(unit.position):
                unit.sprite.spriteOffset = gameStateObj.map.which_border(unit.position)
                unit.sprite.set_transition('fake_out')
            else:
                unit.sprite.set_transition('fade_out')
        elif transition == 'immediate':
            unit.die(gameStateObj, event=event)

    def interact_unit(self, gameStateObj, attacker, defender, event_combat=True):
        if ',' in attacker:
            attacker = gameStateObj.get_unit_from_pos(self.parse_pos(attacker))
        else:
            attacker = gameStateObj.get_unit(attacker)
        if not attacker:
            logger.error('Interact unit routine could not find %s', attacker)
            return

        if ',' in defender:
            def_pos = self.parse_pos(defender)
        else:
            defender = gameStateObj.get_unit(defender)
            if not defender:
                logger.error('Interact unit routine could not find %s', defender)
                return
            def_pos = defender.position

        item = attacker.items[0]
        if not item:
            logger.warning("Attacker does not have a valid item to use in first slot.")
            return
        defender, splash = Interaction.convert_positions(gameStateObj, attacker, attacker.position, def_pos, item)
        gameStateObj.combatInstance = Interaction.start_combat(gameStateObj, attacker, defender, def_pos, splash, item, event_combat=event_combat)
        gameStateObj.stateMachine.changeState('combat')
        self.current_state = "Paused"

    def find_next_position(self, gameStateObj, new_pos, placement, shuffle=True):
        # Determine where the unit will appear
        # Using prev defined reinforcement positions
        if new_pos.startswith('r'):
            new_pos = self.get_rein_position(new_pos[1:], gameStateObj)
        # If coord, use coord
        elif ',' in new_pos:
            new_pos = self.get_position(new_pos)
        # If name, then we want to find a point adjacent to that characters position
        else:
            if new_pos in gameStateObj.allreinforcements:
                new_pos = [gameStateObj.allreinforcements[new_pos][1]]
            else:
                new_pos = [gameStateObj.get_unit_from_id(new_pos).position]

        # Shuffle positions if necessary
        if shuffle:
            random.shuffle(new_pos)

        # Determine which positions I can't move onto
        bad_pos = {bad_unit.position for bad_unit in gameStateObj.allunits if bad_unit.position}

        self.next_position = self.get_final_pos(gameStateObj, placement, new_pos, bad_pos)

    def get_final_pos(self, gameStateObj, placement, new_pos, bad_pos):
        if placement == 'give_up':
            for pos in new_pos:
                if pos not in bad_pos and gameStateObj.map.check_bounds(pos):
                    return pos
        elif placement == 'stack':
            for pos in new_pos:
                if pos not in bad_pos and gameStateObj.map.check_bounds(pos):
                    return pos
            # If we couldn't find a good position to place this unit, just place it anywhere
            return new_pos[0]
        elif placement == 'closest':
            return self.get_closest(new_pos, bad_pos, gameStateObj)
        elif placement == 'closest_f':
            return self.get_closest(new_pos, bad_pos, gameStateObj, flying=True)
        elif placement == 'push':
            for pos in new_pos:
                if pos not in bad_pos and gameStateObj.map.check_bounds(pos):
                    return pos
            # If we couldn't find a good position to place this unit, just push the dude out
            pos = new_pos[0]
            if gameStateObj.map.check_bounds(pos):
                other_unit = gameStateObj.get_unit_from_pos(pos)
                other_unit.push_to_nearest_open_space(gameStateObj)
                return pos
            else:
                return None
        else:
            logger.warning('%s placement not supported.', placement)

    def get_closest(self, new_pos, bad_pos, gameStateObj, flying=False):
        r = 0
        while r < 10:
            for x in range(-r, r + 1):
                for y in [(r - abs(x)), -(r - abs(x))]:
                    for pos in new_pos:
                        check_pos = pos[0] + x, pos[1] + y
                        if check_pos not in bad_pos and gameStateObj.map.check_bounds(check_pos) and \
                           (gameStateObj.map.tiles[check_pos].get_mcost(0) < 5 or
                           (flying and gameStateObj.map.tiles[check_pos].get_mcost(cf.CONSTANTS['flying_mcost_column']) < 5)):
                            return check_pos
            r += 1

    def get_id(self, spec):
        if "," in spec: # If is a coordinate
            return self.parse_pos(spec)
        else:
            return spec

    def parse_pos(self, pos):
        return tuple([int(num) for num in pos.split(',')])

    def get_position(self, pos_line):
        position_list = [self.parse_pos(coord) for coord in pos_line.split('.')]
        return position_list

    def get_rein_position(self, pos_line, gameStateObj):
        rein_positions = [position for position, value in gameStateObj.map.tile_info_dict.iteritems() if 'Reinforcement' in value]
        position_list = [position for position in rein_positions if gameStateObj.map.tile_info_dict[position]['Reinforcement'] == pos_line]
        return position_list

    def add_item(self, unit, item, gameStateObj, banner=True):
        if unit:
            if len(unit.items) < cf.CONSTANTS['max_items']:
                unit.add_item(item)
                if banner:
                    gameStateObj.banners.append(Banner.acquiredItemBanner(unit, item))
                    gameStateObj.stateMachine.changeState('itemgain')
                    self.current_state = "Paused"
            elif unit.team == 'player':
                gameStateObj.convoy.append(item)
                if banner:
                    gameStateObj.banners.append(Banner.sent_to_convoyBanner(item))
                    gameStateObj.stateMachine.changeState('itemgain')
                    self.current_state = "Paused"
        else:
            gameStateObj.convoy.append(item)
            if banner:
                gameStateObj.banners.append(Banner.sent_to_convoyBanner(item))
                gameStateObj.stateMachine.changeState('itemgain')
                self.current_state = "Paused"
        
# === DIALOG CLASS ============================================================
class Dialog(object):
    def __init__(self, text, owner, position, size, font,
                 background='MessageWindowBackground',
                 message_tail=None, num_lines=2, hold=False,
                 waiting_cursor_flag=True, transition=False, unit_sprites=None,
                 slow_flag=1, talk=True):
        self.truetext = text # The actual text this dialog box will display
        self.position = position # The position of the dialog box on its surf
        self.owner = owner # who is saying this
        if unit_sprites:
            self.unit_sprites = unit_sprites # Dictionary of all units on screen
        else:
            self.unit_sprites = {} # Empty dictionary
        self.text = [] # A holding list for the individual characters that will be displayed
        self.text_lines = [] # A holding list for each line that will be displayed
        self.num_lines = num_lines # the max number of lines that may be displayed at a time

        self.scroll_y = 0 # For scroll effect

        self.set_text() # Converts {} style constructs into commands. Adds truetext to text
       
        self.main_font = GC.FONT[font]
        self.current_font = font.split('_')[0]
        self.current_color = font.split('_')[1]

        self._next_line() # Add first line

        self.waiting_cursor = GC.IMAGESDICT['WaitingCursor']
        self.wait_width = self.waiting_cursor.get_width()
        self.waiting_cursor_offset = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1]
        self.waiting_cursor_offset_index = 0
        self.waiting_cursor_flag = waiting_cursor_flag
        self.preempt_break = False

        self.dlog_box = MenuFunctions.CreateBaseMenuSurf(size, background) # Background of text box
        self.topleft = None
        self.text_width = self.dlog_box.get_width() - 16
        self.text_height = self.dlog_box.get_height() - 16
        self.text_surf = Engine.create_surface((self.text_width, self.text_height), transparent=True) # surface that text is drawn on

        self.waiting = False
        self.done = False
        self.talk = talk

        self.total_num_updates = 0

        self.message_tail = message_tail
        self.hold = hold

        # To match dialogue scene, so it can be used in the same place.
        self.transition = transition
        self.transition_transparency = 0
        self.dialog = False

        self.solo_flag = True # Only one of these on the screen at a time
        self.slow_flag = slow_flag # Whether to print the characters out slower than normal

    def set_text(self):
        if self.truetext == '':
            return

        command = None
        self.next_pos = [0, 0]
        for char in self.truetext:
            if char == '{' and command is None:
                command = '{'
            elif char == '}' and command is not None:
                command += '}'
                self.text.insert(0, command)
                command = None
            else:
                if command is not None:
                    command += char
                else:
                    self.text.insert(0, char)

    def _next_line(self):
        self.text_lines.append([])
        self._next_chunk()
        if len(self.text_lines) > self.num_lines:
            self.scroll_y += self.main_font.height

    def _clear(self):
        self.text_lines.append([])
        self._next_chunk()
        self.scroll_y += self.main_font.height*self.num_lines

    def _next_chunk(self):
        self.text_lines[-1].append(['', self.current_color])

    def _add_letter(self, letter):
        self.text_lines[-1][-1][0] += letter
        
    def _next_char(self): # draw the next character
        if self.waiting:
            return True# Wait!
        if self.is_done():
            return True
        text_string = ''.join(self.text)
        text_string = re.sub(r'\{[^}]*\}', ' ', text_string) # Removes things in brackets from the word
        words = text_string.split()
        if words:
            word = words[-1]
        else:
            word = ''
        letter = self.text.pop()
        # print(word[::-1], letter, self.text_width)
        # test for special commands
        if letter == "{br}": # if we've hit a line break command
            if not self.preempt_break:
                self._next_line() # go to next line
            self.preempt_break = False
        elif letter in ["{wait}", "{w}"]:
            self.waiting = True
            both_width = self._get_width('')
            # print(both_width)
            if both_width >= self.text_width - self.wait_width: # if we've exceeded width
                self.preempt_break = True # We've essentially done the next line break
                self._next_line()
            return True # we're waiting
        elif letter == "{clear}":
            self.text.append("{erase}")
            for x in range(self.num_lines-1):
                self.text.append("{br}")
            self._next_line()
        elif letter == "{erase}":
            self.text_lines = []
            self._next_line()
        elif letter == "{black}":
            self.current_color = "black"
            self._next_chunk()            
        elif letter == "{red}":
            self.current_color = "red"
            self._next_chunk()
        else:
            both_width = self._get_width(word)
            # print(letter, previous_lines, word[::-1], both_width, self.text_width)
            if both_width >= self.text_width: # if we've exceeded width
                self._next_line()
                if letter != ' ':
                    self._add_letter(letter)
            else:
                self._add_letter(letter) # Add the letter to the most recent chunk
 
        return False

    def _get_width(self, word):
        font = GC.FONT[self.current_font + '_' + self.current_color]
        # Get text for all chunks in this line
        previous_lines = ''
        for chunk, color in self.text_lines[-1]:
            if chunk != '':
                previous_lines = chunk + previous_lines

        return font.size(previous_lines + word[::-1])[0] # Can actually differ from above due to ligatures/digraphs.
            
    def draw_text(self, surf, pos):
        x, y = pos
        self.scroll_help = int(math.ceil(self.scroll_y/float(self.main_font.height)))
        my_lines = self.text_lines[-(self.num_lines+self.scroll_help):]
        num_lines = len(my_lines) # Used for center
        for index, line in enumerate(my_lines):
            total_length = x
            if self.position == 'center':
                total_length = 0
            for chunk, color in line:
                font = GC.FONT[self.current_font + '_' + color]
                chunk = chunk.strip()
                if chunk != '': # Ignore empty chunks, or else random spaces will appear
                    pos = [total_length, index * font.height + y]
                    total_length += font.size(chunk)[0] + font.size(' ')[0]
                    if self.position == 'center':
                        x_pos = surf.get_width()/2 - total_length/2
                        y_pos = index * font.height + surf.get_height()/2 - num_lines*font.height/2
                        pos = [x_pos, y_pos]
                    font.blit(chunk, surf, pos)
                    if self.position != 'center':
                        pos[0] = total_length
        return pos

    def add_message_tail(self, surf, unit_sprites):
        if self.owner in unit_sprites:
            unit_position = unit_sprites[self.owner].position
        else:
            return
        dialogue_position = self.topleft
        # Do we flip the tail?
        # unit's x _ position < halfway point of screen - half length of unit sprite
        if unit_position[0] < GC.WINWIDTH/2 - 96/2: # On left side
            mirror = True
        else:
            mirror = False
        if mirror:
            tail_surf = Engine.flip_horiz(self.message_tail)
        else:
            tail_surf = self.message_tail
        y_position = dialogue_position[1] + self.dlog_box.get_height() - 2
        # Solve for x_position
        x_position = unit_position[0] + 72 if mirror else unit_position[0] + 16
        # If we wouldn't actually be on the dialogue box
        if x_position > self.dlog_box.get_width() + self.topleft[0] - 24:
            x_position = self.topleft[0] + self.dlog_box.get_width() - 24
        elif x_position < self.topleft[0] + 8: 
            x_position = self.topleft[0] + 8

        surf.blit(tail_surf, (x_position, y_position))

    def add_nametag(self, surf, unit_sprites):
        if self.owner not in unit_sprites and self.owner != "Narrator":
            dialogue_position = self.topleft
            name_tag_surf = MenuFunctions.CreateBaseMenuSurf((64, 16), 'NameTagMenu')
            pos = (dialogue_position[0] - 4, dialogue_position[1] - 10)
            if pos[0] < 0:
                pos = dialogue_position[0] + 16, pos[1]
            position = (name_tag_surf.get_width()/2 - self.main_font.size(self.owner)[0]/2, name_tag_surf.get_height()/2 - self.main_font.size(self.owner)[1]/2)
            self.main_font.blit(self.owner, name_tag_surf, position)
            surf.blit(name_tag_surf, pos)

    def hurry_up(self):
        self.transition = False
        while not self._next_char():
            pass # while we haven't reached the end, process all the next chars...

    def is_done(self):
        if len(self.text) == 0:
            self.done = True
        return self.done

    def update(self):
        if self.transition:
            self.transition_transparency += 1 # 10 is max # 10 frames to load in
            if self.transition_transparency >= 10:
                # Done transitioning
                self.transition = False
                if self.talk and self.owner in self.unit_sprites:
                    self.unit_sprites[self.owner].talk()
        elif self.scroll_y > 0:
            self.scroll_y -= 2
        else:
            num_updates = Engine.get_delta() / float(cf.OPTIONS['Text Speed']*self.slow_flag)
            self.total_num_updates += num_updates
            if self.total_num_updates >= 1:
                # handle the waiting markxzer's vertical offset
                self.waiting_cursor_offset_index += 1
                if self.waiting_cursor_offset_index > len(self.waiting_cursor_offset) - 1:
                    self.waiting_cursor_offset_index = 0

            while self.total_num_updates >= 1:
                self.total_num_updates -= 1
                if self._next_char():
                    self.total_num_updates = 0
                    break

    def draw(self, surf, unit_sprites):
        text_surf = self.text_surf.copy()
        if self.transition:
            scroll = 0
        else:
            end_text_position = self.draw_text(text_surf, (0, 0))
            scroll = int(self.main_font.height*self.scroll_help - self.scroll_y)
        text_surf = Engine.subsurface(text_surf, (0, scroll, text_surf.get_width(), text_surf.get_height() - scroll))
            
        surf_pos = self.position
        if surf_pos == 'center':
            self.topleft = (GC.WINWIDTH/2 - self.dlog_box.get_width()/2, GC.WINHEIGHT/2 - self.dlog_box.get_height()/2)
        else:
            self.topleft = surf_pos

        if self.transition:
            dlog_box = Image_Modification.resize(self.dlog_box, (1, self.transition_transparency/20. + .5))
            dlog_box = Image_Modification.flickerImageTranslucent(dlog_box, 100 - self.transition_transparency*10)
            topleft = self.topleft[0], self.topleft[1] + self.dlog_box.get_height() - dlog_box.get_height()
        else:
            dlog_box = self.dlog_box
            topleft = self.topleft
        surf.blit(dlog_box, topleft)

        if not self.transition:
            if self.message_tail:
                self.add_message_tail(surf, unit_sprites)
            self.add_nametag(surf, unit_sprites)
            surf.blit(text_surf, (self.topleft[0] + 8, self.topleft[1] + 8))

            # Handle the waiting cursor
            # print(end_text_position, scroll)
            if self.waiting and self.waiting_cursor_flag and not self.scroll_y:
                cursor_surf = self.waiting_cursor
                # Make sure we're not placing this where it shouldn't be
                if end_text_position[0] > text_surf.get_width() - self.wait_width:
                    # if end_text_position[1] != 0:
                    #    self._next_line() # Don't do this because it messes up the next line.
                    # print(self.num_lines*self.main_font.height)
                    if end_text_position[1] >= (self.num_lines-1)*self.main_font.height:
                        # self._next_line()
                        pass # For now
                    else:
                        end_text_position[0] = 0 # Move to next line
                        end_text_position[1] += self.main_font.height
                pos = (surf_pos[0] + 8 + end_text_position[0],
                       surf_pos[1] + 16 + end_text_position[1] + self.waiting_cursor_offset[self.waiting_cursor_offset_index])
                surf.blit(cursor_surf, pos)

class Credits(object):
    def __init__(self, title, text, title_font, font, wait=False, center=False):
        self.title = title
        self.text = text
        self.center = center
        self.wait = wait
        self.wait_flag = False
        self.title_font = GC.FONT[title_font]
        self.main_font = GC.FONT[font]

        self.populate_surface()

        self.position = [0, GC.WINHEIGHT]
        self.speed = 0.35

        self.last_update = Engine.get_time()

        # To match dialog
        self.waiting = False # Never waiting on unit input
        self.done = True # Always ready to move on to next dialog object
        self.hold = True # Always show, even if waiting
        self.solo_flag = False # As many of these on the screen as possible
        self.talk = False

    def populate_surface(self):
        index = 0
        self.parsed_text = []
        for credit in self.text:
            x_bound = GC.WINWIDTH - 12 if self.center else GC.WINWIDTH - 88
            l = MenuFunctions.line_wrap(MenuFunctions.line_chunk(credit), x_bound, self.main_font)
            for line in l:
                text = ''.join(line)
                if self.center:
                    x_pos = GC.WINWIDTH/2 - self.main_font.size(text)[0]/2
                else:
                    x_pos = 88
                y_pos = self.main_font.height*index + self.title_font.height
                index += 1
                self.parsed_text.append((text, index, (x_pos, y_pos)))

        self.length = index

        # Create surface
        size = GC.WINWIDTH, self.title_font.height + self.main_font.height * self.length
        self.surface = Engine.create_surface(size, transparent=True)

        # title - center?
        # title_pos_x = GC.WINWIDTH/2 - self.title_font.size(self.title)[0]/2
        title_pos_x = 32
        self.title_font.blit(self.title, self.surface, (title_pos_x, 0))

        # rest of text
        for text, index, pos in self.parsed_text:
            self.main_font.blit(text, self.surface, pos)

    def determine_wait(self):
        time = (self.length + 2) * self.main_font.height / self.speed * 1000/GC.FPS
        if self.wait:
            time += self.get_pause() * 2
        return time

    def get_pause(self):
        return -~self.length * 1000 # 1500

    def hurry_up(self):
        pass

    def is_done(self):
        return self.done

    def update(self):
        # print(self.position)
        current_time = Engine.get_time()
        if not self.wait_flag or current_time - self.last_update > self.get_pause():
            self.wait_flag = False
            self.position[1] -= self.speed
            self.last_update = current_time
            if self.wait and GC.WINHEIGHT/2 - self.surface.get_height()/2 >= self.position[1]:
                self.wait_flag = True
                self.wait = False

    def draw(self, surf, unit_sprites=None):
        self.update()
        surf.blit(self.surface, self.position)

class EndingsDisplay(object):
    def __init__(self, message_system, portrait1, title, text, stats, font='text_white'):
        self.message_system = message_system
        self.portrait = Engine.subsurface(GC.UNITDICT[portrait1 + 'Portrait'], (0, 0, 96, 80))
        self.title = title
        self.truetext = text
        self.text = []
        self.text_index = [0, 0] # Line num, character num within line
        self.font = GC.FONT[font]
        self.format_text(text)

        self.background = GC.IMAGESDICT['EndingsDisplay']
        self.populate_surface(portrait1, stats)

        self.x_position = GC.WINWIDTH

        self.wait_time = len(self.text) * 2000
        self.starting_update = Engine.get_time()
        self.last_update = 0

        # To match dialog
        self.waiting = False # Not waiting on unit input??
        self.done = False # Not always ready to move on to next dialog object
        self.hold = True # Always show, even if waiting
        self.solo_flag = True # Not as many of these as possible
        self.talk = False

    def populate_surface(self, name, stats):
        # Create surface
        size = GC.WINWIDTH, GC.WINHEIGHT
        self.surface = Engine.create_surface(size, transparent=True)

        self.surface.blit(self.background, (0, 0))
        self.surface.blit(self.portrait, (128 + 8, GC.WINHEIGHT - 80 - 24 + 1))

        title_pos_x = 68 - self.font.size(self.title)[0]/2
        self.font.blit(self.title, self.surface, (title_pos_x, 24))

        # === Stats
        # Get statistics
        kills, damage, healing = 0, 0, 0
        for level in stats:
            if name in level.stats:
                record = level.stats[name]
                kills += record['kills']
                damage += record['damage']
                healing += record['healing']

        # Print Statistics
        GC.FONT['text_yellow'].blit('K', self.surface, (136, 8))
        GC.FONT['text_yellow'].blit('D', self.surface, (168, 8))
        GC.FONT['text_yellow'].blit('H', self.surface, (200, 8))
        GC.FONT['text_blue'].blit(str(kills), self.surface, (144, 8))
        dam = str(damage)
        if damage >= 1000:
            dam = dam[:-3] + '.' + dam[-3] + 'k'
        heal = str(healing)
        if healing >= 1000:
            heal = heal[:-3] + '.' + dam[-3] + 'k'
        GC.FONT['text_blue'].blit(dam, self.surface, (176, 8))
        GC.FONT['text_blue'].blit(heal, self.surface, (208, 8))

    def format_text(self, text):
        l = MenuFunctions.line_wrap(MenuFunctions.line_chunk(text), GC.WINWIDTH - 32, self.font)
        for line in l:
            self.text.append(''.join(line))

    def hurry_up(self):
        self.text_index[0] = len(self.text)
        self.done = True
        self.waiting = True

    def is_done(self):
        return self.done

    def update(self):
        current_time = Engine.get_time()
        # Transition in
        if self.x_position > 0:
            self.x_position -= 8
            self.x_position = max(0, self.x_position)

        # Add text -- increment indices counter
        elif self.text_index[0] < len(self.text):
            if current_time - self.last_update > cf.OPTIONS['Text Speed']:
                self.last_update = current_time
                self.text_index[1] += 1
                if self.text_index[1] > len(self.text[self.text_index[0]]) - 1:
                    self.text_index[0] += 1
                    self.text_index[1] = 0

        else:
            self.done = True
            self.waiting = True
            # Only wait for so long
            if current_time - self.starting_update > self.wait_time:
                self.message_system.dialog_unpause() # Move further with process

    def draw(self, surf, unit_sprites=None):
        # self.update()
        surf.blit(self.surface, (self.x_position, 0))

        for index, line in enumerate(self.text[:self.text_index[0]+1]):
            if index == self.text_index[0]:
                new_line = line[:self.text_index[1]]
            else:
                new_line = line
            self.font.blit(new_line, surf, (self.x_position + 16, 48 + index*self.font.height))

class UnitPortrait(object):
    def __init__(self, portrait_name, blink_position, mouth_position, position, priority=0, mirror=False, transition=False, expression='Normal'):
        self.name = portrait_name
        try:
            portrait_sprite = GC.UNITDICT[portrait_name + 'Portrait'].copy()
        except KeyError:
            raise KeyError
        self.halfblink = Engine.subsurface(portrait_sprite, (96, 48, 32, 16))
        self.fullblink = Engine.subsurface(portrait_sprite, (96, 64, 32, 16))
        self.openmouth = Engine.subsurface(portrait_sprite, (0, 96, 32, 16))
        self.halfmouth = Engine.subsurface(portrait_sprite, (32, 96, 32, 16))
        self.closemouth = Engine.subsurface(portrait_sprite, (64, 96, 32, 16))
        self.opensmile = Engine.subsurface(portrait_sprite, (0, 80, 32, 16))
        self.halfsmile = Engine.subsurface(portrait_sprite, (32, 80, 32, 16))
        self.closesmile = Engine.subsurface(portrait_sprite, (64, 80, 32, 16))
        self.portrait = Engine.subsurface(portrait_sprite, (0, 0, 96, 80))

        self.blink_position = blink_position
        self.mouth_position = mouth_position

        self.position = position
        # For movement
        self.new_position = position
        self.isMoving = False
        self.last_move_update = 0
        self.unit_speed = 6
        self.update_time = 25

        self.priority = priority
        self.mirror = mirror
        self.remove_flag = False
        self.image = self.portrait.copy()

        # Talking setup
        self.talking = False
        self.talk_state = 0
        self.last_talk_update = 0
        self.next_talk_update = 0

        # Transition info
        self.transition = 'trans2color' if transition else 0
        self.transition_transparency = 100 if transition else 0  # 100 is most transparency
        self.transition_last_update = Engine.get_time()

        # Blinking set up
        self.blinking = 2 # 0- Don't blink, 1-hold blink, 2-Blink pseudorandomly
        self.offset_blinking = [x for x in range(-2000, 2000, 125)]
        self.blink_counter = Counters.generic3Counter(7000 + random.choice(self.offset_blinking), 40, 40) # 3 frames for each

        # Expression
        self.expression = expression

        # For bop
        self.bops_remaining = 0
        self.bop_state = False
        self.last_bop = None

    def talk(self):
        self.talking = True

    def stop_talking(self):
        self.talking = False

    def move(self, new_position):
        self.new_position = self.new_position[0] + new_position[0], self.new_position[1] + new_position[1]
        self.isMoving = True

    def bop(self):
        self.bops_remaining = 2
        self.bop_state = False
        self.last_bop = Engine.get_time()

    def update(self, current_time=None):
        if not current_time:
            current_time = Engine.get_time()
        # update mouth
        if self.talking and current_time - self.last_talk_update > self.next_talk_update:
            self.last_talk_update = current_time
            chance = random.randint(1, 10)
            if self.talk_state == 0:
                # 10% chance to skip to state 2    
                if chance == 1:
                    self.talk_state = 2
                    self.next_talk_update = random.randint(70, 160)
                else:
                    self.talk_state = 1
                    self.next_talk_update = random.randint(30, 50)
            elif self.talk_state == 1:
                # 10% chance to go back to state 0
                if chance == 1:
                    self.talk_state = 0
                    self.next_talk_update = random.randint(50, 100)
                else:
                    self.talk_state = 2
                    self.next_talk_update = random.randint(70, 160)
            elif self.talk_state == 2:
                # 10% chance to skip back to state 0
                # 10% chance to go back to state 1
                chance = random.randint(1, 10)
                if chance == 1:
                    self.talk_state = 0
                    self.next_talk_update = random.randint(50, 100)
                elif chance == 2:
                    self.talk_state = 1
                    self.next_talk_update = random.randint(30, 50)
                else:
                    self.talk_state = 3
                    self.next_talk_update = random.randint(30, 50)
            elif self.talk_state == 3:
                self.talk_state = 0
                self.next_talk_update = random.randint(50, 100)

        if not self.talking:
            self.talk_state = 0

        if self.blinking == 2:
            self.blink_counter.update(current_time)

        if self.transition:
            if self.transition == 'trans2color':
                self.transition_transparency = 100 - (current_time - self.transition_last_update)/2 # 12 frames
            elif self.transition == 'color2trans':
                self.transition_transparency = (current_time - self.transition_last_update)/2 # 12 frames
            if self.transition_transparency > 100 or self.transition_transparency < 0:
                self.transition = 0
                self.transition_transparency = max(0, min(100, self.transition_transparency))
                # Done transitioning to invisibility, so remove me!
                if self.remove_flag:
                    return True

        # Move unit if he/she needs to be moved
        if self.isMoving:
            if current_time - self.last_move_update > self.update_time:
                # Finds difference between new_position and position
                diff_pos = (self.new_position[0] - self.position[0], self.new_position[1] - self.position[1])
                # No longer moving if difference of position is small
                if diff_pos[0] <= self.unit_speed and diff_pos[0] >= -self.unit_speed and diff_pos[1] <= self.unit_speed and diff_pos[1] >= -self.unit_speed:
                    # Close enough for gov't work
                    self.position = self.new_position
                    self.isMoving = False
                else:
                    angle = math.atan2(diff_pos[1], diff_pos[0])
                    updated_position = (self.position[0] + self.unit_speed * math.cos(angle), self.position[1] + self.unit_speed * math.sin(angle))
                    self.position = updated_position

                self.last_move_update = current_time

        # Bop unit if he/she needs to be bopped
        if self.bops_remaining:
            if current_time - self.last_bop > 150:
                self.last_bop = current_time
                if self.bop_state:
                    self.position = self.position[0], self.position[1] - 2
                    self.bops_remaining -= 1
                else:
                    self.position = self.position[0], self.position[1] + 2
                self.bop_state = not self.bop_state
                
        return False

    def create_image(self):
        if self.blinking == 2:
            if self.blink_counter.count == 0:
                if self.expression == "Full_Blink":
                    self.image.blit(self.fullblink, self.blink_position)
                elif self.expression == "Half_Blink":
                    self.image.blit(self.halfblink, self.blink_position)
                else:
                    self.image = self.portrait.copy()
            elif self.blink_counter.count == 1:
                if self.expression == "Full_Blink":
                    self.image.blit(self.fullblink, self.blink_position)
                else:
                    self.image.blit(self.halfblink, self.blink_position)
            elif self.blink_counter.count == 2:
                self.image.blit(self.fullblink, self.blink_position)
        elif self.blinking == 1:
            self.image.blit(self.fullblink, self.blink_position)

        if self.talk_state == 0:
            if self.expression == "Smiling":
                self.image.blit(self.closesmile, self.mouth_position)
            else:
                self.image.blit(self.closemouth, self.mouth_position)
        elif self.talk_state == 1 or self.talk_state == 3:
            if self.expression == "Smiling":
                self.image.blit(self.halfsmile, self.mouth_position)
            else:
                self.image.blit(self.halfmouth, self.mouth_position)
        elif self.talk_state == 2:
            if self.expression == "Smiling":
                self.image.blit(self.opensmile, self.mouth_position) 
            else:
                self.image.blit(self.openmouth, self.mouth_position)
        
    def draw(self, surf):
        self.create_image()    
        # === MODS ===
        image_sprite = self.image.copy()

        if self.transition:
            image_sprite = Image_Modification.flickerImageBlackColorKey(image_sprite, self.transition_transparency)

        # Mirror if necessary...
        if self.mirror:
            image_sprite = Engine.flip_horiz(image_sprite)
        surf.blit(image_sprite, self.position)

    def remove(self):
        self.transition = 'color2trans'
        self.remove_flag = True
        self.transition_last_update = Engine.get_time()

#! usr/bin/env
import re, random, math, itertools
# Custom imports
import GlobalConstants as GC
import configuration as cf
import CustomObjects, MenuFunctions, SaveLoad, Image_Modification, StatusObject, Counters, LevelUp
import Interaction, ItemMethods, WorldMap, Utility, UnitObject, Engine, Banner

import logging
logger = logging.getLogger(__name__)

# === GET INFO FOR DIALOGUE SCENE ==================================================
class Dialogue_Scene(object):
    def __init__(self, scene, optionalunit=None, optionalunit2=None, tile_pos=None, event_flag=True, if_flag=False):
        self.scene = scene
        self.scene_lines = []
        with open(scene, 'r') as scenefp: # Open this scene's file database
            for line in scenefp: # Get lines
                self.scene_lines.append(line)
        if cf.OPTIONS['debug']: 
            if not self.count_if_statements():
                logger.error('ERROR: Incorrect number of if and end statements! %s', scene)

        # Background sprite
        self.background = None
        self.foreground = None
        # Dictionary of scene sprites
        self.unit_sprites = {}
        # Dialogs
        self.dialog = []
        
        # Optional unit
        self.optionalunit = optionalunit
        if self.optionalunit and isinstance(self.optionalunit, UnitObject.UnitObject):
            # logger.debug('locking %s', self.optionalunit)
            self.optionalunit.lock_active()
        self.optionalunit2 = optionalunit2
        if self.optionalunit2 and isinstance(self.optionalunit2, UnitObject.UnitObject):
            # logger.debug('locking %s', self.optionalunit2)
            self.optionalunit2.lock_active()
        # Tile Pos -- What tile is being acted on, if any (applied in unlock, switch, village, destroy, search, etc. scripts)
        self.tile_pos = tile_pos
        # Next Position -- Saves a position
        self.next_position = None
        # Whether we are processing
        self.processingflag = 1
        # Whether we are done
        self.done = False
        # Whether we are waiting for user input
        self.waiting = False

        # For transition
        self.transition = 0 # No transition
        self.transition_transparency = 0 # 0 is transparent
        self.transition_last_update = Engine.get_time()

        self.scene_lines_index = 0 # Keeps track of where we are in the scene
        self.if_stack = [] # Keeps track of how many ifs we've encountered while searching for
        # the bad ifs 'end'.
        self.parse_stack = [] # Keeps track of whether we've encountered a truth this level or not

        self.current_state = "Processing" # Other options are "Waiting", "Transitioning"
        self.last_state = None
        self.do_skip = False
        self.battle_save_flag = False # Whether to enter the battle save state after this scene has completed
        self.reset_state_flag = False # Whether to reset state to free state after this scene has completed
        self.reset_boundary_manager = False # Whether to reset the boundary manager after this scene has completed. Set to true when tiles are changed

        # Handle waiting
        self.last_wait_update = Engine.get_time()
        self.waittime = 0

        # Handles skipping
        self.skippable_commands = {'s', 'u', 'qu', 't', 'c', 'wait',
                                   'wm_move_sprite', 'map_pan', 'set_expression',
                                   'credits', 'endings', 'start_move'}

        # Handles unit face priority
        self.priority_counter = 1

        # Does this count as an event
        self.event_flag = event_flag
        # Assumes all "if" statements evaluate to True?
        self.if_flag = if_flag

    def serialize(self):
        return self.scene

    def count_if_statements(self):
        if_count = 0
        for line in self.scene_lines:
            new_line = line.strip()
            if new_line.startswith('if;'):
                if_count += 1
            elif new_line.startswith('end'):
                if_count -= 1
        return not bool(if_count) # Should be 0.

    def skip(self):
        self.do_skip = True
        if not self.current_state == "Paused":
            self.current_state = "Processing"
        # Reset transition
        self.transition = 0
        self.transition_transparency = 0

    def read_script(self, gameStateObj=None, metaDataObj=None):
        while(self.scene_lines_index < len(self.scene_lines) and self.current_state == "Processing"):
            line = self.scene_lines[self.scene_lines_index]
            # Doing some parsing of the lines
            if len(line) <= 0:
                self.scene_lines_index += 1
                continue

            line = line.strip()
            if line.startswith('#'):
                self.scene_lines_index += 1
                continue

            line = line.split(';')
            # logger.debug(line)
            if (self.if_flag or self.handle_if(line, gameStateObj, metaDataObj)) and (not self.do_skip or not (line[0] in self.skippable_commands)):
                self.parse_line(line, gameStateObj, metaDataObj)
            self.scene_lines_index += 1

    def handle_if(self, line, gameStateObj, metaDataObj):
        # if cf.OPTIONS['debug']: print(line[0], self.if_stack)
        # === CONDITIONALS PARSING
        if line[0] == 'if':
            if not self.if_stack or self.if_stack[-1]:
                truth = eval(line[1])
                self.if_stack.append(truth)
                self.parse_stack.append(truth) # Whether we've encountered a truth this level
            else:
                self.if_stack.append(False)
                self.parse_stack.append(True)
            return False
        elif line[0] == 'elif':
            if not self.if_stack:
                logger.error("Syntax Error somewhere in script. 'elif' needs to be after if statement.")
                return # Impossible. Must have at least parsed an if before hitting an elif
            # If we haven't encountered a truth yet
            if not self.parse_stack[-1]: # if not self.if_stack[-1] and (len(self.if_stack) == 1 or all(t_value for t_value in self.if_stack[:-1])):
                truth = eval(line[1])
                self.if_stack[-1] = truth
                self.parse_stack[-1] = truth
            else:
                self.if_stack[-1] = False
            return False
        elif line[0] == 'else':
            if not self.if_stack:
                logger.error("Syntax Error somewhere in script. 'else' needs to be after if statement.")
                return # Impossible. Must have at least parsed an if before hitting an else
            # If the most recent is False but the rest below are non-existent or true
            if not self.parse_stack[-1]: # not self.if_stack[-1] and (len(self.if_stack) == 1 or all(t_value for t_value in self.if_stack[:-1])):
                self.if_stack[-1] = True
                self.parse_stack[-1] = True
            else:
                self.if_stack[-1] = False # Parse stack stays the same
            return False
        elif line[0] == 'end':
            self.if_stack.pop()
            self.parse_stack.pop()
            return False

        if self.if_stack and not self.if_stack[-1]:
            return False
        return True

    def update(self, gameStateObj=None, metaDataObj=None):
        current_time = Engine.get_time()
        if self.current_state != self.last_state:
            self.last_state = self.current_state
            logger.debug('Dialogue Current State: %s', self.current_state)

        if self.current_state == "Waiting":
            if current_time > self.last_wait_update + self.waittime:
                self.current_state = "Processing" # Done waiting. Head back to processing
            else:
                return # Keep waiting. Do nothing

        elif self.current_state == "Processing":
            self.reset_unit_sprites() # Stop talking when grabbing data - probably only affected at end of file
            # parse sceneLines
            self.read_script(gameStateObj, metaDataObj)
            if self.scene_lines_index >= len(self.scene_lines):
                self.end()

        elif self.current_state == "Transitioning":
            if self.transition == 1: # Increasing
                self.transition_transparency = current_time - self.transition_last_update
            elif self.transition == 2:
                self.transition_transparency = 255 - (current_time - self.transition_last_update)
            elif self.transition == 3:
                self.transition_transparency = (current_time - self.transition_last_update)/2
            elif self.transition == 4:
                self.transition_transparency = 255 - (current_time - self.transition_last_update)/2
            if self.transition_transparency > 255 + 5*GC.FRAMERATE or self.transition_transparency < 0: # I want 5 extra frames in black
                self.current_state = "Processing"
                if self.scene_lines_index >= len(self.scene_lines): # Check if we're done
                    self.end()
            self.transition_transparency = Utility.clamp(self.transition_transparency, 0, 255)

        elif self.current_state == "Displaying": # Don't dialog while transitioning or processing
            if self.dialog: # Only update dialog if it exists, could also just be waiting or not even in this state
                self.dialog[-1].update() # This runs the character generation...
                if self.dialog[-1].done and not self.dialog[-1].waiting:
                    self.current_state = "Processing"

        elif self.current_state == "Paused":
            pass

        if self.dialog and (self.dialog[-1].waiting or self.dialog[-1].done):
            self.reset_unit_sprites()

    def end(self):
        self.done = True
        if self.optionalunit and isinstance(self.optionalunit, UnitObject.UnitObject):
            # logger.debug('Unlocking %s', self.optionalunit)
            self.optionalunit.unlock_active()
        if self.optionalunit2 and isinstance(self.optionalunit2, UnitObject.UnitObject):
            # logger.debug('Unlocking %s', self.optionalunit2)
            self.optionalunit2.unlock_active()
        return

    def parse_line(self, line, gameStateObj=None, metaDataObj=None):
        if line[0] == '':
            return
        # time = Engine.get_true_time()
        logger.info('Script line to parse: %s', line)
        # === SKIPPING DIALOGUE
        # End skip
        if line[0] == 'end_skip':
            self.do_skip = False

        # === PAUSE
        elif line[0] == 'wait':
            self.waittime = int(line[1])
            self.last_wait_update = Engine.get_time()
            self.current_state = "Waiting"

        # === BACKGROUND
        # Change the background
        elif line[0] == 'b':
            if 'map' in line:
                self.background = WorldMap.WorldMapBackground(GC.IMAGESDICT[line[1]], labels=False)
            elif 'advanced' in line:
                self.background = WorldMap.WorldMapBackground(GC.IMAGESDICT[line[1]])
            else:
                self.background = MenuFunctions.StaticBackground(GC.IMAGESDICT[line[1]], fade=False)
        # Remove the background
        elif line[0] == 'remove_background':
            self.background = None

        # === FOREGROUND
        # Change the foreground
        elif line[0] == 'foreground':
            self.foreground = MenuFunctions.StaticBackground(GC.IMAGESDICT[line[1]], fade=False)
        elif line[0] == 'remove_foreground':
            self.foreground = None

        # === WORLD MAP
        elif line[0] == 'wm_move':
            new_position = self.parse_pos(line[1])
            self.background.move(new_position)
        elif line[0] == 'wm_qmove':
            new_position = self.parse_pos(line[1])
            self.background.quick_move(new_position)
        elif line[0] == 'wm_load_sprite' or line[0] == 'wm_load' or line[0] == 'wm_add':
            starting_position = self.parse_pos(line[2])
            if line[0] == 'wm_add':
                starting_position = (starting_position[0]*16, starting_position[1]*16)
            if 'custom' in line[1]:
                klass, gender, team = line[3:6]
            else:
                for unit in gameStateObj.allunits:
                    if unit.name == line[1]:
                        klass = unit.klass
                        gender = 'M' if unit.gender < 5 else 'F'
                        team = unit.team
                        break
                else:
                    logger.warning("Couldn't find unit matching line: %s", line)
                    return
            self.background.add_sprite(line[1], klass, gender, team, starting_position)
        elif line[0] == 'wm_remove_sprite' or line[0] == 'wm_remove':
            self.background.remove_sprite(line[1])
        elif line[0] == 'wm_move_sprite':
            new_position = self.parse_pos(line[2])
            self.background.move_sprite(line[1], new_position)
        elif line[0] == 'wm_label':
            name = line[1]
            position = self.parse_pos(line[2])
            self.background.add_label(name, position)
        elif line[0] == 'wm_label_clear':
            self.background.clear_labels()
        elif line[0] == 'wm_highlight':
            name = line[1]
            self.background.add_highlight(GC.IMAGESDICT['Highlight' + name])
        elif line[0] == 'wm_highlight_clear':
            self.background.clear_highlights()
        elif line[0] == 'wm_cursor':
            pos = self.parse_pos(line[1])
            self.background.create_cursor(pos)
        elif line[0] == 'wm_remove_cursor':
            self.background.remove_cursor()

        # === UNIT SPRITE
        # Add a unit to the scene
        elif line[0] == 'u':
            # This is a complicated method of parsing unit lines using 'u' as delimeter
            spl = []
            w = 'u'
            for x, y in itertools.groupby(line, lambda z: z == w):
                if x:
                    spl.append([])
                spl[-1].extend(y)

            for sub_command in spl:
                if self.add_unit_sprite(sub_command, metaDataObj, transition=True):
                    # Force wait after unit sprite is drawn to allow time to transition.
                    self.waittime = 250
                    self.last_wait_update = Engine.get_time()
                    self.current_state = "Waiting"
        # Add a unit to the scene without transition
        elif line[0] == 'qu':
            self.add_unit_sprite(line, metaDataObj, transition=False)
        # Change a unit's expression
        elif line[0] == 'set_expression':
            self.unit_sprites[line[1]].expression = line[2]
        # Remove a unit from the scene
        elif line[0] == 'r':
            for name in line[1:]:
                unit_name = self.optionalunit.name if name == '{unit}' else name
                if unit_name in self.unit_sprites:
                    self.unit_sprites[unit_name].remove()
                    # Force wait after unit sprite is drawn to allow time to transition.
                    self.waittime = 250
                    self.last_wait_update = Engine.get_time()
                    self.current_state = "Waiting"
        elif line[0] == 'qr': # No transition plox
            for name in line[1:]:
                unit_name = self.optionalunit.name if name == '{unit}' else name
                if unit_name in self.unit_sprites:
                    self.unit_sprites.pop(unit_name)
        # Change a units position
        elif line[0] == 'c':
            name = self.optionalunit.name if line[1] == '{unit}' else line[1]
            self.unit_sprites.pop(name) # Remove line
            self.add_unit_sprite(line, metaDataObj, transition=False)
        # Change the priority of a unit
        elif line[0] == 'change_priority':
            name = self.optionalunit.name if line[1] == '{unit}' else line[1]
            if name in self.unit_sprites:
                self.unit_sprites[name].priority = int(line[2])
        # Move a unit sprite
        elif line[0] == 'move_sprite':
            name = self.optionalunit.name if line[1] == '{unit}' else line[1]
            if name in self.unit_sprites:
                new_position = self.parse_pos(line[2])
                self.unit_sprites[name].move(new_position)
                # Force wait after unit sprite is moved to allow time to transition
                if 'force_hold' in line:
                    self.waittime = abs(new_position[0]/self.unit_sprites[name].unit_speed*self.unit_sprites[name].update_time)+200
                    self.last_wait_update = Engine.get_time()
                    self.current_state = "Waiting"
        # Mirror the unit sprite
        elif line[0] == 'mirror':
            name = self.optionalunit.name if line[1] == '{unit}' else line[1]
            if name in self.unit_sprites:
                self.unit_sprites[name].mirror = not self.unit_sprites[name].mirror
        # Bop the unit sprite up and down
        elif line[0] == 'bop':
            name = self.optionalunit.name if line[1] == '{unit}' else line[1]
            if name in self.unit_sprites:
                self.unit_sprites[name].bop()

        # === MUSIC
        # Fade in this musical accompanienment
        elif line[0] == 'm':
            if line[1] in GC.MUSICDICT:
                logger.debug('Fade in %s', line[1])
                Engine.music_thread.fade_in(GC.MUSICDICT[line[1]])
            else:
                logger.warning("Couldn't find music matching %s", line[1])
        elif line[0] == 'mf':
            logger.debug('Fade out music')
            Engine.music_thread.fade_back()

        # === HANDLE ITEMS
        # Give the optional unit an item or give the unit named in the line the item
        elif line[0] == 'give_item' or line[0] == 'add_item':
            # Find receiver
            if line[1] == '{unit}' and self.optionalunit:
                receiver = self.optionalunit
            elif line[1] == 'Convoy':
                receiver = None
            else:
                receiver = gameStateObj.get_unit_from_name(line[1])
            # Append item to list of units items
            if line[2] != "0":
                item = ItemMethods.itemparser(line[2])[0]
                self.add_item(receiver, item, gameStateObj, len(line) <= 3)
            elif line[2] == "0":
                gameStateObj.banners.append(Banner.foundNothingBanner(receiver))
                gameStateObj.stateMachine.changeState('itemgain')
                self.current_state = "Paused"

        # Has the unit equip an item in their inventory if and only if that item is in their inventory by name
        elif line[0] == 'equip_item':
            if line[1] == '{unit}':
                name = self.optionalunit.name
            else:
                name = line[1]
            for unit in gameStateObj.allunits:
                if unit.name == name:
                    for item in unit.items:
                        if item.name == line[2]:
                            unit.equip(item)
                            break
                break
        # Give the gameStateObj GC.COLORDICT['gold']!
        elif line[0] == 'gold':
            gameStateObj.counters['money'] += int(line[1])
            gameStateObj.banners.append(Banner.acquiredGoldBanner(int(line[1])))
            gameStateObj.stateMachine.changeState('itemgain')
            self.current_state = "Paused"

        elif line[0] == 'remove_item':
            if line[1] == '{unit}':
                item = [item for item in self.optionalunit.items if item.name == line[2] or item.id == line[2]][0]
                self.optionalunit.remove_item(item)
            else:
                for unit in gameStateObj.allunits:
                    if unit.name == line[1]:
                        item = [item for item in self.optionalunit.items if item.name == line[2] or item.id == line[2]][0]
                        unit.remove_item(item)
                        break

        # Add a skill/status to a unit
        elif line[0] == 'give_skill':
            skill = StatusObject.statusparser(line[2])
            if line[1] == '{unit}' and self.optionalunit:
                StatusObject.HandleStatusAddition(skill, self.optionalunit, gameStateObj)
                if 'no_display' not in line:
                    gameStateObj.banners.append(Banner.gainedSkillBanner(self.optionalunit, skill))
                    gameStateObj.stateMachine.changeState('itemgain')
                    self.current_state = "Paused"
            else:
                for unit in gameStateObj.allunits: # Find the unit this is supposed to go to
                    if unit.name == line[1]:
                        # print(unit.name)
                        StatusObject.HandleStatusAddition(skill, unit, gameStateObj)
                        if 'no_display' not in line:
                            gameStateObj.banners.append(Banner.gainedSkillBanner(unit, skill))
                            gameStateObj.stateMachine.changeState('itemgain')
                            self.current_state = "Paused"
                        break

        # Give exp to a unit
        elif line[0] == 'exp_gain':
            exp = int(line[2])
            c_unit = None
            if line[1] == '{unit}' and self.optionalunit:
                c_unit = self.optionalunit
            else:
                for unit in gameStateObj.allunits:
                    if unit.name == line[1]:
                        c_unit = unit
            gameStateObj.levelUpScreen.append(LevelUp.levelUpScreen(gameStateObj, unit=c_unit, exp=exp))
            gameStateObj.stateMachine.changeState('expgain')
            self.current_state = "Paused"

        # === HANDLES UNITS ON MAP
        elif line[0] == 'add_unit':
            # Read input
            which_unit = line[1]
            to_which_position = line[2] if len(line) > 2 else None
            transition = line[3] if (len(line) > 3 and line[3]) else 'fade'
            placement = line[4] if (len(line) > 4 and line[4]) else 'give_up'
            order = True if len(line) > 5 else False 
            self.add_unit(gameStateObj, metaDataObj, which_unit, to_which_position, transition, placement, shuffle=not order)
        elif line[0] == 'create_unit':
            # Read input
            which_unit = line[1]
            level = eval(line[2])
            to_which_position = line[3] if len(line) > 3 else None
            transition = line[4] if (len(line) > 4 and line[4]) else 'fade'
            placement = line[5] if (len(line) > 5 and line[5]) else 'give_up'
            order = True if len(line) > 6 else False 
            self.add_unit(gameStateObj, metaDataObj, which_unit, to_which_position, transition, placement, shuffle=not order, create=level)
        elif line[0] == 'move_unit':
            # Read input
            which_unit = line[1]
            to_which_position = line[2]
            transition = line[3] if (len(line) > 3 and line[3]) else 'normal'
            placement = line[4] if (len(line) > 4 and line[4]) else 'give_up'
            order = True if len(line) > 5 else False
            self.move_unit(gameStateObj, metaDataObj, which_unit, to_which_position, transition, placement, shuffle=not order)
        elif line[0] == 'start_move':
            self.current_state = "Paused"
            gameStateObj.stateMachine.changeState('movement')
        elif line[0] == 'interact_unit':
            # Read input
            attacker = line[1]
            defender = line[2]
            event_combat = False if len(line) > 3 else True
            self.interact_unit(gameStateObj, attacker, defender, event_combat)
        elif line[0] == 'remove_unit' or line[0] == 'kill_unit':
            # Read input
            which_unit = line[1]
            transition = line[2] if (len(line) > 2 and line[2]) else 'fade'
            event = (line[0] == 'remove_unit')
            self.remove_unit(gameStateObj, which_unit, transition, event=event)
        elif line[0] == 'set_next_position':
            to_which_position = line[1]
            placement = line[2] if len(line) > 2 else 'give_up'
            order = True if len(line) > 3 else False
            self.find_next_position(gameStateObj, to_which_position, placement, shuffle=not order)

        # === HANDLE CURSOR
        elif line[0] in ['set_cursor', 'move_cursor']:
            if line[1].startswith('o') and "," in line[1]:
                coord = self.parse_pos(line[1][1:])
                if gameStateObj.map.custom_origin:
                    coord = coord[0] + gameStateObj.map.custom_origin[0], coord[1] + gameStateObj.map.custom_origin[1]
            elif "," in line[1]: # If is a coordinate
                coord = self.parse_pos(line[1])
            elif line[1] == 'next' and self.next_position:
                coord = self.next_position
            elif line[1] == '{unit}' and self.optionalunit:
                    coord = self.optionalunit.position
            else:
                for unit in gameStateObj.allunits:
                    if (unit.name == line[1] or unit.event_id == line[1]) and unit.position:
                        coord = unit.position
                        break
                else:
                    logger.error("Couldn't find unit %s", line[1])
                    return
            gameStateObj.cursor.setPosition(coord, gameStateObj)
            if (line[0] == 'move_cursor' or 'force_hold' in line) and not self.do_skip:
                gameStateObj.stateMachine.changeState('move_camera')
                self.current_state = "Paused"
        # Display Cursor 1 is yes, 0 is no
        elif line[0] == 'disp_cursor' or line[0] == 'display_cursor':
            choice_flag = int(line[1])
            if choice_flag:
                gameStateObj.cursor.drawState = 1
            else:
                gameStateObj.cursor.drawState = 0
        elif line[0] == 'set_camera':
            pos1 = self.parse_pos(line[1])
            pos2 = self.parse_pos(line[2])
            gameStateObj.cameraOffset.center2(pos1, pos2)
            if len(line) > 3 and line[3] == 'force':  # Force
                pass
            else:
                gameStateObj.stateMachine.changeState('move_camera')      
                self.current_state = "Paused"
        elif line[0] == 'tutorial_mode':
            if line[1] == '0':
                gameStateObj.tutorial_mode_off()
            else:
                gameStateObj.tutorial_mode = line[1]
        elif line[0] == 'fake_cursor':
            coords = line[1:]
            for text_coord in coords:
                coord = [int(num) for num in text_coord.split(',')]
                gameStateObj.fake_cursors.append(CustomObjects.Cursor('Cursor', coord, fake=True))
        elif line[0] == 'remove_fake_cursors':
            gameStateObj.remove_fake_cursors()
        elif line[0] == 'set_camera_pan':
            choice_flag = int(line[1])
            if choice_flag:
                gameStateObj.cameraOffset.pan_flag = True
            else:
                gameStateObj.cameraOffset.pan_flag = False
        elif line[0] == 'map_pan':
            gameStateObj.cameraOffset.pan_flag = True
            gameStateObj.cameraOffset.map_pan(gameStateObj.map, gameStateObj.cursor.position)
            gameStateObj.stateMachine.changeState('move_camera')
            self.current_state = 'Paused'

        # === HANDLE OBJECTIVE
        elif line[0] == 'change_objective_display_name':
            gameStateObj.objective.display_name_string = line[1]
        elif line[0] == 'change_objective_win_condition':
            gameStateObj.objective.win_condition_string = line[1]
        elif line[0] == 'change_objective_loss_condition':
            gameStateObj.objective.loss_condition_string = line[1]
        elif line[0] == 'add_minimum_number_banner':
            gameStateObj.banners.append(Banner.tooFewUnitsBanner())
            gameStateObj.stateMachine.changeState('itemgain')
            self.current_state = "Paused"
        elif line[0] == 'switch_pulled_banner':
            gameStateObj.banners.append(Banner.switchPulledBanner())
            gameStateObj.stateMachine.changeState('itemgain')
            self.current_state = "Paused"
        elif line[0] == 'lose_game':
            gameStateObj.statedict['levelIsComplete'] = 'loss'
        elif line[0] == 'win_game':
            gameStateObj.statedict['levelIsComplete'] = 'win'
        elif line[0] == 'skip_outro':
            gameStateObj.statedict['outroScriptDone'] = True

        elif line[0] == 'battle_save':
            # Using a flag instead of just going to battle save state because if I save while
            # there's a dialogue state on the stack, the dialogue has a surface which crashes the save
            # This problem might not exist anymore, but...
            self.battle_save_flag = True
        elif line[0] == 'reset_state':
            self.reset_state_flag = True

        # === HANDLE TILE CHANGES -- These get put in the command list
        elif line[0] == 'set_custom_origin':
            if len(line) > 1:
                gameStateObj.map.custom_origin = self.parse_pos(line[1])
                line = [line[0], self.parse_pos(line[1])]
            else:
                gameStateObj.map.custom_origin = self.tile_pos
                line.append(self.tile_pos)
            gameStateObj.map.command_list.append(line)
        # Change tile sprites. - command, pos, tile_sprite, size, transition
        elif line[0] == 'change_tile_sprite' or line[0] == 'change_sprite':
            # Add default transition
            if len(line) < 4:
                line.append('fade')
            gameStateObj.map.change_sprite(line)
            # Ways of destroying. Defaults to instantaneous in command list
            if 'fade' in line:
                line.remove('fade')
            elif 'destroy' in line:
                line.remove('destroy')
            gameStateObj.map.command_list.append(line)
        elif line[0] == 'layer_tile_sprite':
            gameStateObj.map.layer_tile_sprite(line)
            gameStateObj.map.command_list.append(line)
        elif line[0] == 'show_layer':
            if len(line) < 3:
                line.append('fade')
            gameStateObj.map.show_layer(line)
            if 'fade' in line:
                line.remove('fade')
            elif 'destroy' in line:
                line.remove('destroy')
            gameStateObj.map.command_list.append(line)
        elif line[0] == 'hide_layer':
            if len(line) < 3:
                line.append('fade')
            gameStateObj.map.hide_layer(line)
            if 'fade' in line:
                line.remove('fade')
            elif 'destroy' in line:
                line.remove('destroy')
            gameStateObj.map.command_list.append(line)
        elif line[0] == 'clear_layer':
            gameStateObj.map.clear_layer(line[1])
            gameStateObj.map.command_list.append(line)
        # Change one tile
        elif line[0] == 'replace_tile':
            coords = gameStateObj.map.replace_tile(line, gameStateObj.grid_manager)
            gameStateObj.map.command_list.append(line)
            self.reset_boundary_manager = True
            # gameStateObj.boundary_manager.reset(gameStateObj)
        # Change area of tile (must include pic instead of id)
        elif line[0] == 'area_replace_tile':
            coord, size = gameStateObj.map.mass_replace_tile(line, gameStateObj.grid_manager)
            gameStateObj.map.command_list.append(line)
            self.reset_boundary_manager = True
            # width, height = size
            # gameStateObj.boundary_manager.reset(gameStateObj)
        # Change one tile's information
        elif line[0] == 'set_tile_info':
            gameStateObj.map.set_tile_info(line)
            gameStateObj.map.command_list.append(line)
        # Changing whole map tile data!
        elif line[0] == 'load_new_map_tiles':
            gameStateObj.map.load_new_map_tiles(line, gameStateObj.counters['level'])
            gameStateObj.map.command_list.append(line)
            gameStateObj.boundary_manager.reset(gameStateObj)
        # Changing whole map's sprites!
        elif line[0] == 'load_new_map_sprite':
            gameStateObj.map.load_new_map_sprite(line, gameStateObj.counters['level'])
            gameStateObj.map.command_list.append(line)
        # Reset whole map's tile_info!
        elif line[0] == 'reset_map_tile_info':
            gameStateObj.map.reset_tile_info()
            gameStateObj.map.command_list.append(line)
        # Add weather
        elif line[0] == 'add_weather':
            gameStateObj.map.add_weather(line[1])
            gameStateObj.map.command_list.append(line)
        # Remove weather
        elif line[0] == 'remove_weather':
            gameStateObj.map.remove_weather(line[1])
            gameStateObj.map.command_list.append(line)
        # Add global status
        elif line[0] == 'add_global_status':
            gameStateObj.map.add_status(line[1])
            gameStateObj.map.command_list.append(line)
        # Remove global status
        elif line[0] == 'remove_global_status':
            gameStateObj.map.remove_status(line[1])
            gameStateObj.map.command_list.append(line)
        # Remove global status
        # Clear command list
        elif line[0] == 'clear_command_list':
            if len(line) > 1:
                gameStateObj.map.command_list = [command for command in gameStateObj.map.command_list if command[0] != line[1]]
            else:
                gameStateObj.map.command_list = []
        elif line[0] == 'clear_command_list_except':
            gameStateObj.map.command_list = [command for command in gameStateObj.map.command_list if command[0] == line[1]]

        # === CLEANUP
        elif line[0] == 'arrange_formation':
            if len(line) > 1:  # force arrange
                player_units = [unit for unit in gameStateObj.allunits if unit.team == 'player' and
                                not unit.dead]
                formation_spots = [pos for pos, value in gameStateObj.map.tile_info_dict.iteritems()
                                   if 'Formation' in value]
            else:
                player_units = [unit for unit in gameStateObj.allunits if unit.team == 'player' and
                                not unit.dead and not unit.position]
                formation_spots = [pos for pos, value in gameStateObj.map.tile_info_dict.iteritems()
                                   if 'Formation' in value and not gameStateObj.grid_manager.get_unit_node(pos)]
            for index, unit in enumerate(player_units[:len(formation_spots)]):
                if len(line) > 1:
                    unit.leave(gameStateObj)
                    unit.remove_from_map(gameStateObj)
                unit.position = formation_spots[index]
                # print(unit.name, unit.position)
                unit.place_on_map(gameStateObj)
                unit.arrive(gameStateObj)
        elif line[0] == 'reset_units':
            for unit in gameStateObj.allunits:
                unit.reset()
        elif line[0] == 'reset_unit':
            if line[1] == '{unit}':
                self.optionalunit.reset()
            else:
                for unit in gameStateObj.allunits:
                    if unit.id == line[1] or unit.event_id == line[1] or unit.name == line[1] or unit.team == line[1]:
                        unit.reset()
        elif line[0] == 'remove_enemies':
            if len(line) > 1:
                exception = line[1]
            else:
                exception = None
            # Remove enemies
            gameStateObj.allunits = [unit for unit in gameStateObj.allunits if unit.team == 'player' or unit.name == exception]
        elif line[0] == 'kill_all':
            if len(line) > 1:
                call_out = line[1]
            else:
                call_out = None
            for unit in gameStateObj.allunits:
                if unit.position and unit.team == call_out:
                    unit.isDying = True
                    gameStateObj.stateMachine.changeState('dying')
        # === GAME CONSTANTS
        # should be remembered for map
        elif line[0] == 'trigger_event':
            gameStateObj.event_triggers.append(line[1])
        elif line[0] == 'resolve_event':
            if line[1] in gameStateObj.event_triggers:
                gameStateObj.event_triggers.remove(line[1])
        # should be remembered for all game
        elif line[0] == 'add_game_constant' or line[0] == 'set_game_constant':
            gameStateObj.game_constants.append(line[1])
        elif line[0] == 'unlock_lore':
            gameStateObj.unlocked_lore.append(line[1])
        elif line[0] == 'remove_lore':
            if line[1] in gameStateObj.unlocked_lore:
                del gameStateObj.unlocked_lore[line[1]]
        elif line[0] == 'set_counter':
            gameStateObj.counters[line[1]] = 0
            if len(line) > 2:
                gameStateObj.counters[line[1]] = int(eval(line[2]))
        elif line[0] == 'inc_counter':
            if line[1] in gameStateObj.counters:
                if len(line) > 2:
                    gameStateObj.counters[line[1]] += int(eval(line[2]))
                else:
                    gameStateObj.counters[line[1]] += 1
            else:
                logger.warning('No counter named %s found in gameStateObj.counters! Did you remember to set it?', line[1])
        elif line[0] == 'metaDataObj':
            gameStateObj.metaDataObj_changes.append(line)
            metaDataObj[line[1]] = line[2]
            if line[1].endswith('Music'):
                metaDataObj[line[1]] = GC.MUSICDICT[line[2]]
        elif line[0] == 'event_flag':
            self.event_flag = bool(int(line[1]))
        elif line[0] == 'add_to_market':
            gameStateObj.market_items.add(line[1])
        elif line[0] == 'remove_from_market':
            gameStateObj.market_items.discard(line[1])
            
        # === TRANSITIONS
        elif line[0] == 't': # Handle transition
            if line[1] == '1':
                self.transition = 1
                self.transition_transparency = 0 # Increasing
            elif line[1] == '2':
                self.transition = 2
                self.transition_transparency = 255 # Decreasing
            elif line[1] == '3':
                self.transition = 3
                self.transition_transparency = 0
            elif line[1] == '4':
                self.transition = 4
                self.transition_transparency = 255
            self.transition_last_update = Engine.get_time()
            self.current_state = "Transitioning"
        
        # === CHANGING UNITS
        elif line[0] == 'convert':
            unit_specifier = self.get_id(line[1])
            for unit in gameStateObj.allunits:
                if unit.name == unit_specifier or unit.event_id == unit_specifier or unit.position == unit_specifier:
                    unit.changeTeams(line[2], gameStateObj)
        elif line[0] == 'change_class':
            unit_specifier = self.get_id(line[1])
            for unit in gameStateObj.allunits:
                if unit.name == unit_specifier or unit.event_id == unit_specifier or unit.position == unit_specifier:
                    unit.changeClass(line[2], gameStateObj)
        elif line[0] == 'change_ai':
            unit_specifier = self.get_id(line[1])
            for unit in gameStateObj.allunits:
                if unit.name == unit_specifier or unit.event_id == unit_specifier or unit.position == unit_specifier:
                    unit.ai_descriptor = line[2]
                    unit.get_ai(line[2])
        elif line[0] == 'add_tag':
            unit_specifier = self.get_id(line[1])
            for unit in gameStateObj.allunits:
                if unit.name == unit_specifier or unit.event_id == unit_specifier or unit.position == unit_specifier:
                    unit.tags.add(line[2])

        # === HANDLE TALKING
        elif line[0] == 'add_talk':
            # Add to dictionary
            gameStateObj.talk_options.append((line[1], line[2]))
        elif line[0] == 'remove_talk':
            if (line[1], line[2]) in gameStateObj.talk_options:
                gameStateObj.talk_options.remove((line[1], line[2]))
        elif line[0] == 'set_base_convo':
            # Add to dictionary
            gameStateObj.base_conversations[line[1]] = True
        elif line[0] == 'remove_base_convo':
            if line[1] in gameStateObj.base_conversations:
                del gameStateObj.base_conversations[line[1]]
        elif line[0] == 'grey_base_convo':
            if line[1] in gameStateObj.base_conversations:
                gameStateObj.base_conversations[line[1]] = False
            
        # === DIALOGUE BOX
        # Add line of text          
        elif line[0] == 's':
            self.evaluate_evals(line, gameStateObj)
            self.add_dialog(line)
        # === CREDITS BOX
        elif line[0] == 'credits':
            self.add_credits(line)
        # === ENDINGS BOX
        elif line[0] == 'endings':
            self.add_ending(line, gameStateObj)
        # === Pop dialog off top
        elif line[0] == 'pop_dialog':
            if self.dialog:
                self.dialog.pop()

        # === Show Victory Screen
        elif line[0] == 'victory_screen':
            gameStateObj.stateMachine.changeState('victory')
            self.current_state = 'Paused'
        # === ROLL CREDITS
        elif line[0] == 'roll_credits':
            gameStateObj.stateMachine.changeState('credits')
            self.current_state = "Paused"
        # === Display Records
        elif line[0] == 'records_display':
            gameStateObj.stateMachine.changeState('base_records')
            self.current_state = "Paused"

        # logger.info('Time taken: %s', Engine.get_true_time() - time)

    def evaluate_evals(self, line, gameStateObj):
        # Evaluate evals
        if '{eval:' in line[2]:
            dialog = line[2][:]
            last_index = None
            add = False
            command = []
            # Traverse backwards so we don't mess up the indexing
            for i, char in reversed(list(enumerate(dialog))):
                if char == '}':
                    last_index = i
                    add = True
                elif char == '{':
                    command = ''.join(command)
                    if command.startswith('eval:'):
                        to_eval = command[5:]
                        result = str(eval(to_eval))
                        line[2] = line[2][:i] + result + line[2][last_index+1:]
                    add = False
                    command = []
                elif add:
                    command.insert(0, char)
        return line[2]

    def add_dialog(self, line):
        speaker = self.optionalunit.name if line[1] == '{unit}' else line[1]
        tail = None
        waiting_cursor = True
        transition = False
        thought_bubble = False
        if 'hint' in line:
            if 'auto' in line:
                position = GC.WINWIDTH/4, GC.WINHEIGHT/4
                size = GC.WINWIDTH/2 + 8, GC.WINHEIGHT/2
            else:
                position = [int(line[3]), int(line[4])]
                size = ((int(line[5]) if line[5] else GC.WINWIDTH/2 + 8), GC.WINHEIGHT/2)
            back_surf = 'Parchment_Window'
            font = 'convo_black'
            num_lines = 4
        elif 'narration' in line:
            if 'auto' in line:
                position = 4, 110
                size = 232, 48
            else:
                position = [int(line[3]), int(line[4])]
                size = int(line[5]) if len(line) > 5 else 144, 48
            back_surf = 'BaseMenuBackground'
            font = 'convo_white'
            num_lines = 2
        elif 'cinematic' in line:
            position = 'center'
            size = GC.WINWIDTH, GC.WINHEIGHT
            back_surf = 'ActualTransparent'
            font = 'chapter_grey'
            num_lines = 5
            waiting_cursor = False
        else:
            owner = self.unit_sprites[speaker] if speaker in self.unit_sprites else None
            # --- Quick fix
            if len(line) == 3:
                # s, speaker, text -- Assume we forgot {w} auto
                line[2] += '{w}'
                line.append('auto')

            if 'auto' in line:
                position, size = self.auto_dialog_box(line[2], owner)
            else:
                position = [int(line[3]), int(line[4])]
                size = int(line[5]) if len(line) > 5 else 144, 48
            if 'noir' in line:
                back_surf = 'NoirMessageWindow'

            else:
                back_surf = 'MessageWindowBackground'
            if 'thought_bubble' in line:
                thought_bubble = True
            if 'noir' in line:
                tail = None
            elif thought_bubble:
                tail = GC.IMAGESDICT['ThoughtWindowTail'] if owner else None
            else:
                tail = GC.IMAGESDICT['MessageWindowTail'] if owner else None
            font = 'convo_black'
            if 'noir' in line:
                font = 'convo_white'
            num_lines = 2
            transition = True
        self.dialog.append(Dialog(line[2], speaker, position, size, font, back_surf, tail, num_lines,
                                  waiting_cursor_flag=waiting_cursor, transition=transition,
                                  unit_sprites=self.unit_sprites, slow_flag=3 if 'slow' in line else 1,
                                  talk=not thought_bubble))

        self.reset_unit_sprites()
        if self.dialog[-1].owner in self.unit_sprites:
            self.unit_sprites[self.dialog[-1].owner].priority = self.priority_counter
            self.priority_counter += 1

        # Stop processing
        self.current_state = "Displaying"

    def auto_dialog_box(self, dialogue, owner):
        position = [8, 104]
        length = GC.WINWIDTH - 8*2
        num_lines = 2
        if owner:
            # Split on breaks and clears
            longest_dialogue_size = 0
            current_dialogue = []
            command = []
            in_command = False
            for character in dialogue:
                if character == '{':
                    in_command = True

                if in_command:
                    command.append(character)
                else:
                    current_dialogue.append(character)

                if character == '}':
                    in_command = False
                    command_text = ''.join(command)
                    if command_text == '{clear}' or command_text == '{br}':
                        current_text = ''.join(current_dialogue)
                        # Now find out how big this needs to be to hold this
                        width = self.determine_width(current_text, num_lines)
                        if width > longest_dialogue_size:
                            longest_dialogue_size = width
                        # Clear current dialogue
                        current_dialogue = []
                    # Clear command
                    command = []
            # And do it at the end
            current_text = ''.join(current_dialogue)
            # Now find out how big this needs to be to hold this
            width = self.determine_width(current_text, num_lines)
            if width > longest_dialogue_size:
                longest_dialogue_size = width

            length = longest_dialogue_size + 8*2
            if owner.position[0] < 0:
                position = [8, 24]
            elif owner.position[0] < 24:
                position = [24, 24]
            elif owner.position[0] > 144:
                position = [GC.WINWIDTH - length - 8, 24]
            elif owner.position[0] > 120:
                position = [GC.WINWIDTH - length - 24, 24]
            elif owner.position[0] >= 24 and owner.position[0] < 56:
                position = [40, 24]
            elif owner.position[0] <= 120 and owner.position[0] > 88:
                position = [min(GC.WINWIDTH - length - 40, 96), 24]
            else:
                position = [120 - length/2, 24]
            # Error checking
            if position[0] < 8:
                position[0] = 8
            if position[0] + length > GC.WINWIDTH - 8:
                length = GC.WINWIDTH - 8 - position[0]
        return position, (length, 48)

    def determine_width(self, text, num_lines):
        for w in range(32, GC.WINWIDTH - 8*5, 8):
            # print('width', w)
            output_lines = MenuFunctions.line_wrap(MenuFunctions.line_chunk(text), w, GC.FONT['convo_black'], test=True)
            if len(output_lines) <= num_lines:
                return w + 16 # This is an extra buffer to account for waiting cursor
        # If we got here, it was too bug
        logger.warning('Text too big for dialog box!')
        # print('Text too big for dialog box!')
        return GC.WINWIDTH - 8*4

    def add_credits(self, line):
        title = line[1]
        text = line[2:]
        if 'wait' in text:
            text.remove('wait')
            wait = True
        else:
            wait = False
        if 'center' in text:
            text.remove('center')
            center = True
        else:
            center = False
        title_font = 'credittitle_white'
        font = 'credit_white'

        new_credits = Credits(title, text, title_font, font, wait, center)
        # Wait a certain amount of time before next one
        self.waittime = new_credits.determine_wait()
        self.last_wait_update = Engine.get_time()
        self.current_state = "Waiting"

        self.dialog.append(new_credits)

    def add_ending(self, line, gameStateObj):
        portrait = line[1]
        title = line[2]
        text = line[3]
        font = 'text_white'

        new_ending = EndingsDisplay(self, portrait, title, text, gameStateObj.statistics, font)

        self.dialog.append(new_ending)

        # Stop processing
        self.current_state = "Displaying"

    def draw(self, surf):
        # Draw if background exists
        if self.background:
            # Blit Background
            self.background.draw(surf)

        # Update unit sprites -- if results in true, delete the unit sprite. -- faciliates 'r' command fade out
        delete = [key for key, unit in self.unit_sprites.iteritems() if unit.update()]
        for key in delete: 
            del self.unit_sprites[key]

        # === SCENE SPRITES ===
        # Blit sprites, sort them by their priority (ascending, so 2 is pasted after and on top of 1)
        sorted_sprites = sorted([unit for key, unit in self.unit_sprites.iteritems()], key=lambda x: x.priority)
        for unit in sorted_sprites:
            unit.draw(surf)

        if self.dialog and (self.current_state == "Displaying" or self.dialog[-1].hold):# Draw text (Don't draw text while transitioning)
            for dialog in reversed(self.dialog):
                dialog.draw(surf, self.unit_sprites)
                if dialog.solo_flag:
                    break

        if self.foreground:
            self.foreground.draw(surf)

        s = GC.IMAGESDICT['BlackBackground'].copy()
        s.fill((0, 0, 0, self.transition_transparency))
        surf.blit(s, (0, 0))

    def add_unit_sprite(self, line, metaDataObj, transition=False):
        name = self.optionalunit.name if line[1] == '{unit}' else line[1]
        if name in self.unit_sprites and not self.unit_sprites[name].remove_flag:
            return False
        hardset_positions = {'OffscreenLeft': -96, 'FarLeft': -24, 'Left': 0, 'MidLeft': 24,
                             'MidRight': 120, 'Right': 144, 'FarRight': 168, 'OffscreenRight': 240}
        if line[2] in hardset_positions:
            position = [hardset_positions[line[2]], 80]
            priority = self.priority_counter
            self.priority_counter += 1
            mirrorflag = True if line[2] in ['OffscreenLeft', 'FarLeft', 'Left', 'MidLeft'] else False
            if 'mirror' in line:
                mirrorflag = not mirrorflag
        else:
            position = [int(line[2]), int(line[3])]
            priority = int(line[4])
            mirrorflag = True if 'mirror' in line else False
        if 'Full_Blink' in line:
            expression = 'Full_Blink'
        elif 'Smiling' in line:
            expression = 'Smiling'
        else:
            expression = 'Normal'
        assert name in metaDataObj['portrait_dict'], "%s not in portrait dictionary. Need to assign blink and mouth positions to pic"%(name)
        blink = metaDataObj['portrait_dict'][name]['blink']
        mouth = metaDataObj['portrait_dict'][name]['mouth']
        self.unit_sprites[name] = UnitPortrait(portrait_name=name, blink_position=blink, mouth_position=mouth, transition=transition,
                                               position=position, priority=priority, mirror=mirrorflag, expression=expression)

        return True

    def reset_unit_sprites(self):
        for key, unit in self.unit_sprites.iteritems():
            unit.stop_talking()

    def dialog_unpause(self):
        # Removes waiting from dialog
        if not self.dialog[-1].done and not self.dialog[-1].is_done():
            if self.dialog[-1].talk and self.dialog[-1].owner in self.unit_sprites:
                self.unit_sprites[self.dialog[-1].owner].talk()
        self.dialog[-1].waiting = False

    def add_unit(self, gameStateObj, metaDataObj, which_unit, new_pos, transition, placement, shuffle=True, create=None):
        # Find unit
        if create:
            unitLine = gameStateObj.prefabs.get(which_unit)
            if not unitLine:
                logger.warning('Could not find %s' in unitLine)
                return
            new_unitLine = unitLine[:]
            new_unitLine.insert(4, create)
            unit = SaveLoad.create_unit(new_unitLine, gameStateObj.allunits, gameStateObj.groups, gameStateObj.allreinforcements, metaDataObj, gameStateObj)
            position = self.parse_pos(unitLine[5])
        else:
            context = gameStateObj.allreinforcements.get(which_unit)
            if not context:
                logger.warning('Could not find %s in allreinforcements', which_unit)
                return
            u_id, position = context
            unit = gameStateObj.get_unit_from_id(u_id)
            if unit.dead:
                logger.warning('Unit %s is dead', u_id)
                return
        if not unit:
            logger.error('Could not find unit %s', which_unit)
            return

        # Determine where the unit will appear
        # If none, then use position in load
        if not new_pos:
            new_pos = [position]
        # Using prev defined reinforcement positions
        elif new_pos.startswith('r'):
            new_pos = self.get_rein_position(new_pos[1:], gameStateObj)
        # Using next position
        elif new_pos == 'next':
            new_pos = [self.next_position]
        # If coord, use coord
        elif ',' in new_pos:
            new_pos = self.get_position(new_pos)
        # If name, then we want to find a point adjacent to that characters position
        else:
            new_char = gameStateObj.get_unit_from_id(new_pos)
            if new_char:
                new_pos = [new_char.position]
            else:
                logger.error('Could not find unit %s', new_pos)
                return

        # Shuffle positions if necessary
        if shuffle:
            random.shuffle(new_pos)

        # Determine which positions I can't move onto
        bad_pos = {bad_unit.position for bad_unit in gameStateObj.allunits if bad_unit.position}

        final_pos = self.get_final_pos(gameStateObj, placement, new_pos, bad_pos)
        if not final_pos:
            logger.warning('Could not add to position %s', new_pos)
            return

        if self.do_skip:
            transition = 'immediate'
        # Now we have final pos
        unit.position = final_pos
        if transition == 'warp':
            unit.sprite.set_transition('warp_in')
            gameStateObj.map.initiate_warp_flowers(final_pos)
        elif transition == 'fade':
            if gameStateObj.map.on_border(final_pos):
                unit.sprite.spriteOffset = [num*GC.TILEWIDTH for num in gameStateObj.map.which_border(final_pos)]
                unit.sprite.set_transition('fake_in')
            else:
                unit.sprite.set_transition('fade_in')
        elif transition == 'immediate':
            pass
        unit.place_on_map(gameStateObj)
        unit.arrive(gameStateObj)

    def move_unit(self, gameStateObj, metaDataObj, which_unit, new_pos, transition, placement, shuffle=True):
        # Find unit
        if which_unit == '{unit}':
            unit = self.optionalunit
        elif ',' in which_unit:
            unit = gameStateObj.get_unit_from_pos(self.parse_pos(which_unit))
        else:
            unit = gameStateObj.get_unit(which_unit)
        if not unit:
            logger.error('Move unit routine could not find unit %s', which_unit)
            return
        if not unit.position:
            logger.error('Unit does not have position! %s %s', which_unit, unit.name)
            print('Unit does not have position! %s %s' %(which_unit, unit.name))
            gameStateObj.display_all_units()
            return

        # Determine available positions to move to
        # Using prev defined reinforcement positions
        if new_pos.startswith('r'):
            new_pos = self.get_rein_position(new_pos[1:], gameStateObj)
        # Using next position
        elif new_pos == 'next':
            new_pos = [self.next_position]
        # Using prev position
        elif new_pos == 'prev_pos':
            new_pos = [unit.previous_position]
        # If coord, use coord
        elif ',' in new_pos:
            new_pos = self.get_position(new_pos)
        # If name, then we want to find a point adjacent to that characters position
        else:
            new_pos = [gameStateObj.get_unit_from_id(new_pos).position]

        # Shuffle positions if necessary
        if shuffle:
            random.shuffle(new_pos)

        # Determine which positions I can't move onto
        bad_pos = {bad_unit.position for bad_unit in gameStateObj.allunits if bad_unit.position}

        final_pos = self.get_final_pos(gameStateObj, placement, new_pos, bad_pos)
        if not final_pos:
            logger.warning('Could not move to position %s', new_pos)
            return

        if self.do_skip:
            transition = 'immediate'
        if transition == 'normal':
            movePath = unit.getPath(gameStateObj, final_pos)
            unit.beginMovement(gameStateObj, movePath)
        elif transition == 'warp':
            unit.sprite.set_transition('warp_move')
            unit.sprite.set_next_position(final_pos)
        elif transition == 'fade':
            unit.sprite.set_transition('fade_move')
            unit.sprite.set_next_position(final_pos)
        elif transition == 'immediate':
            unit.leave(gameStateObj)
            unit.position = final_pos
            unit.arrive(gameStateObj)

    def remove_unit(self, gameStateObj, which_unit, transition, event=True):
        # Find unit
        if which_unit == '{unit}':
            unit = self.optionalunit
        elif ',' in which_unit:
            unit = gameStateObj.get_unit_from_pos(self.parse_pos(which_unit))
        else:
            unit = gameStateObj.get_unit(which_unit)
        if not unit:
            logger.error('Remove unit routine could not find unit %s', which_unit)
            return
        if not unit.position:
            logger.warning('Remove unit routine - No position')
            return

        if self.do_skip:
            transition = 'immediate'
        if transition == 'warp':
            unit.sprite.set_transition('warp_out')
        elif transition == 'fade':
            if event and gameStateObj.map.on_border(unit.position):
                unit.sprite.spriteOffset = gameStateObj.map.which_border(unit.position)
                unit.sprite.set_transition('fake_out')
            else:
                unit.sprite.set_transition('fade_out')
        elif transition == 'immediate':
            unit.die(gameStateObj, event=event)

    def interact_unit(self, gameStateObj, attacker, defender, event_combat=True):
        if ',' in attacker:
            attacker = gameStateObj.get_unit_from_pos(self.parse_pos(attacker))
        else:
            attacker = gameStateObj.get_unit(attacker)
        if not attacker:
            logger.error('Interact unit routine could not find %s', attacker)
            return

        if ',' in defender:
            def_pos = self.parse_pos(defender)
        else:
            defender = gameStateObj.get_unit(defender)
            if not defender:
                logger.error('Interact unit routine could not find %s', defender)
                return
            def_pos = defender.position

        item = attacker.items[0]
        if not item:
            logger.warning("Attacker does not have a valid item to use in first slot.")
            return
        defender, splash = Interaction.convert_positions(gameStateObj, attacker, attacker.position, def_pos, item)
        gameStateObj.combatInstance = Interaction.start_combat(gameStateObj, attacker, defender, def_pos, splash, item, event_combat=event_combat)
        gameStateObj.stateMachine.changeState('combat')
        self.current_state = "Paused"

    def find_next_position(self, gameStateObj, new_pos, placement, shuffle=True):
        # Determine where the unit will appear
        # Using prev defined reinforcement positions
        if new_pos.startswith('r'):
            new_pos = self.get_rein_position(new_pos[1:], gameStateObj)
        # If coord, use coord
        elif ',' in new_pos:
            new_pos = self.get_position(new_pos)
        # If name, then we want to find a point adjacent to that characters position
        else:
            if new_pos in gameStateObj.allreinforcements:
                new_pos = [gameStateObj.allreinforcements[new_pos][1]]
            else:
                new_pos = [gameStateObj.get_unit_from_id(new_pos).position]

        # Shuffle positions if necessary
        if shuffle:
            random.shuffle(new_pos)

        # Determine which positions I can't move onto
        bad_pos = {bad_unit.position for bad_unit in gameStateObj.allunits if bad_unit.position}

        self.next_position = self.get_final_pos(gameStateObj, placement, new_pos, bad_pos)

    def get_final_pos(self, gameStateObj, placement, new_pos, bad_pos):
        if placement == 'give_up':
            for pos in new_pos:
                if pos not in bad_pos and gameStateObj.map.check_bounds(pos):
                    return pos
        elif placement == 'stack':
            for pos in new_pos:
                if pos not in bad_pos and gameStateObj.map.check_bounds(pos):
                    return pos
            # If we couldn't find a good position to place this unit, just place it anywhere
            return new_pos[0]
        elif placement == 'closest':
            return self.get_closest(new_pos, bad_pos, gameStateObj)
        elif placement == 'closest_f':
            return self.get_closest(new_pos, bad_pos, gameStateObj, flying=True)
        elif placement == 'push':
            for pos in new_pos:
                if pos not in bad_pos and gameStateObj.map.check_bounds(pos):
                    return pos
            # If we couldn't find a good position to place this unit, just push the dude out
            pos = new_pos[0]
            if gameStateObj.map.check_bounds(pos):
                other_unit = gameStateObj.get_unit_from_pos(pos)
                other_unit.push_to_nearest_open_space(gameStateObj)
                return pos
            else:
                return None
        else:
            logger.warning('%s placement not supported.', placement)

    def get_closest(self, new_pos, bad_pos, gameStateObj, flying=False):
        r = 0
        while r < 10:
            for x in range(-r, r + 1):
                for y in [(r - abs(x)), -(r - abs(x))]:
                    for pos in new_pos:
                        check_pos = pos[0] + x, pos[1] + y
                        if check_pos not in bad_pos and gameStateObj.map.check_bounds(check_pos) and \
                           (gameStateObj.map.tiles[check_pos].get_mcost(0) < 5 or
                           (flying and gameStateObj.map.tiles[check_pos].get_mcost(cf.CONSTANTS['flying_mcost_column']) < 5)):
                            return check_pos
            r += 1

    def get_id(self, spec):
        if "," in spec: # If is a coordinate
            return self.parse_pos(spec)
        else:
            return spec

    def parse_pos(self, pos):
        return tuple([int(num) for num in pos.split(',')])

    def get_position(self, pos_line):
        position_list = [self.parse_pos(coord) for coord in pos_line.split('.')]
        return position_list

    def get_rein_position(self, pos_line, gameStateObj):
        rein_positions = [position for position, value in gameStateObj.map.tile_info_dict.iteritems() if 'Reinforcement' in value]
        position_list = [position for position in rein_positions if gameStateObj.map.tile_info_dict[position]['Reinforcement'] == pos_line]
        return position_list

    def add_item(self, unit, item, gameStateObj, banner=True):
        if unit:
            if len(unit.items) < cf.CONSTANTS['max_items']:
                unit.add_item(item)
                if banner:
                    gameStateObj.banners.append(Banner.acquiredItemBanner(unit, item))
                    gameStateObj.stateMachine.changeState('itemgain')
                    self.current_state = "Paused"
            elif unit.team == 'player':
                gameStateObj.convoy.append(item)
                if banner:
                    gameStateObj.banners.append(Banner.sent_to_convoyBanner(item))
                    gameStateObj.stateMachine.changeState('itemgain')
                    self.current_state = "Paused"
        else:
            gameStateObj.convoy.append(item)
            if banner:
                gameStateObj.banners.append(Banner.sent_to_convoyBanner(item))
                gameStateObj.stateMachine.changeState('itemgain')
                self.current_state = "Paused"
        
# === DIALOG CLASS ============================================================
class Dialog(object):
    def __init__(self, text, owner, position, size, font,
                 background='MessageWindowBackground',
                 message_tail=None, num_lines=2, hold=False,
                 waiting_cursor_flag=True, transition=False, unit_sprites=None,
                 slow_flag=1, talk=True):
        self.truetext = text # The actual text this dialog box will display
        self.position = position # The position of the dialog box on its surf
        self.owner = owner # who is saying this
        if unit_sprites:
            self.unit_sprites = unit_sprites # Dictionary of all units on screen
        else:
            self.unit_sprites = {} # Empty dictionary
        self.text = [] # A holding list for the individual characters that will be displayed
        self.text_lines = [] # A holding list for each line that will be displayed
        self.num_lines = num_lines # the max number of lines that may be displayed at a time

        self.scroll_y = 0 # For scroll effect

        self.set_text() # Converts {} style constructs into commands. Adds truetext to text
       
        self.main_font = GC.FONT[font]
        self.current_font = font.split('_')[0]
        self.current_color = font.split('_')[1]

        self._next_line() # Add first line

        self.waiting_cursor = GC.IMAGESDICT['WaitingCursor']
        self.wait_width = self.waiting_cursor.get_width()
        self.waiting_cursor_offset = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1]
        self.waiting_cursor_offset_index = 0
        self.waiting_cursor_flag = waiting_cursor_flag
        self.preempt_break = False

        self.dlog_box = MenuFunctions.CreateBaseMenuSurf(size, background) # Background of text box
        self.topleft = None
        self.text_width = self.dlog_box.get_width() - 16
        self.text_height = self.dlog_box.get_height() - 16
        self.text_surf = Engine.create_surface((self.text_width, self.text_height), transparent=True) # surface that text is drawn on

        self.waiting = False
        self.done = False
        self.talk = talk

        self.total_num_updates = 0

        self.message_tail = message_tail
        self.hold = hold

        # To match dialogue scene, so it can be used in the same place.
        self.transition = transition
        self.transition_transparency = 0
        self.dialog = False

        self.solo_flag = True # Only one of these on the screen at a time
        self.slow_flag = slow_flag # Whether to print the characters out slower than normal

    def set_text(self):
        if self.truetext == '':
            return

        command = None
        self.next_pos = [0, 0]
        for char in self.truetext:
            if char == '{' and command is None:
                command = '{'
            elif char == '}' and command is not None:
                command += '}'
                self.text.insert(0, command)
                command = None
            else:
                if command is not None:
                    command += char
                else:
                    self.text.insert(0, char)

    def _next_line(self):
        self.text_lines.append([])
        self._next_chunk()
        if len(self.text_lines) > self.num_lines:
            self.scroll_y += self.main_font.height

    def _clear(self):
        self.text_lines.append([])
        self._next_chunk()
        self.scroll_y += self.main_font.height*self.num_lines

    def _next_chunk(self):
        self.text_lines[-1].append(['', self.current_color])

    def _add_letter(self, letter):
        self.text_lines[-1][-1][0] += letter
        
    def _next_char(self): # draw the next character
        if self.waiting:
            return True# Wait!
        if self.is_done():
            return True
        text_string = ''.join(self.text)
        text_string = re.sub(r'\{[^}]*\}', ' ', text_string) # Removes things in brackets from the word
        words = text_string.split()
        if words:
            word = words[-1]
        else:
            word = ''
        letter = self.text.pop()
        # print(word[::-1], letter, self.text_width)
        # test for special commands
        if letter == "{br}": # if we've hit a line break command
            if not self.preempt_break:
                self._next_line() # go to next line
            self.preempt_break = False
        elif letter in ["{wait}", "{w}"]:
            self.waiting = True
            both_width = self._get_width('')
            # print(both_width)
            if both_width >= self.text_width - self.wait_width: # if we've exceeded width
                self.preempt_break = True # We've essentially done the next line break
                self._next_line()
            return True # we're waiting
        elif letter == "{clear}":
            self.text.append("{erase}")
            for x in range(self.num_lines-1):
                self.text.append("{br}")
            self._next_line()
        elif letter == "{erase}":
            self.text_lines = []
            self._next_line()
        elif letter == "{black}":
            self.current_color = "black"
            self._next_chunk()            
        elif letter == "{red}":
            self.current_color = "red"
            self._next_chunk()
        else:
            both_width = self._get_width(word)
            # print(letter, previous_lines, word[::-1], both_width, self.text_width)
            if both_width >= self.text_width: # if we've exceeded width
                self._next_line()
                if letter != ' ':
                    self._add_letter(letter)
            else:
                self._add_letter(letter) # Add the letter to the most recent chunk
 
        return False

    def _get_width(self, word):
        font = GC.FONT[self.current_font + '_' + self.current_color]
        # Get text for all chunks in this line
        previous_lines = ''
        for chunk, color in self.text_lines[-1]:
            if chunk != '':
                previous_lines = chunk + previous_lines

        return font.size(previous_lines + word[::-1])[0] # Can actually differ from above due to ligatures/digraphs.
            
    def draw_text(self, surf, pos):
        x, y = pos
        self.scroll_help = int(math.ceil(self.scroll_y/float(self.main_font.height)))
        my_lines = self.text_lines[-(self.num_lines+self.scroll_help):]
        num_lines = len(my_lines) # Used for center
        for index, line in enumerate(my_lines):
            total_length = x
            if self.position == 'center':
                total_length = 0
            for chunk, color in line:
                font = GC.FONT[self.current_font + '_' + color]
                chunk = chunk.strip()
                if chunk != '': # Ignore empty chunks, or else random spaces will appear
                    pos = [total_length, index * font.height + y]
                    total_length += font.size(chunk)[0] + font.size(' ')[0]
                    if self.position == 'center':
                        x_pos = surf.get_width()/2 - total_length/2
                        y_pos = index * font.height + surf.get_height()/2 - num_lines*font.height/2
                        pos = [x_pos, y_pos]
                    font.blit(chunk, surf, pos)
                    if self.position != 'center':
                        pos[0] = total_length
        return pos

    def add_message_tail(self, surf, unit_sprites):
        if self.owner in unit_sprites:
            unit_position = unit_sprites[self.owner].position
        else:
            return
        dialogue_position = self.topleft
        # Do we flip the tail?
        # unit's x _ position < halfway point of screen - half length of unit sprite
        if unit_position[0] < GC.WINWIDTH/2 - 96/2: # On left side
            mirror = True
        else:
            mirror = False
        if mirror:
            tail_surf = Engine.flip_horiz(self.message_tail)
        else:
            tail_surf = self.message_tail
        y_position = dialogue_position[1] + self.dlog_box.get_height() - 2
        # Solve for x_position
        x_position = unit_position[0] + 72 if mirror else unit_position[0] + 16
        # If we wouldn't actually be on the dialogue box
        if x_position > self.dlog_box.get_width() + self.topleft[0] - 24:
            x_position = self.topleft[0] + self.dlog_box.get_width() - 24
        elif x_position < self.topleft[0] + 8: 
            x_position = self.topleft[0] + 8

        surf.blit(tail_surf, (x_position, y_position))

    def add_nametag(self, surf, unit_sprites):
        if self.owner not in unit_sprites and self.owner != "Narrator":
            dialogue_position = self.topleft
            name_tag_surf = MenuFunctions.CreateBaseMenuSurf((64, 16), 'NameTagMenu')
            pos = (dialogue_position[0] - 4, dialogue_position[1] - 10)
            if pos[0] < 0:
                pos = dialogue_position[0] + 16, pos[1]
            position = (name_tag_surf.get_width()/2 - self.main_font.size(self.owner)[0]/2, name_tag_surf.get_height()/2 - self.main_font.size(self.owner)[1]/2)
            self.main_font.blit(self.owner, name_tag_surf, position)
            surf.blit(name_tag_surf, pos)

    def hurry_up(self):
        self.transition = False
        while not self._next_char():
            pass # while we haven't reached the end, process all the next chars...

    def is_done(self):
        if len(self.text) == 0:
            self.done = True
        return self.done

    def update(self):
        if self.transition:
            self.transition_transparency += 1 # 10 is max # 10 frames to load in
            if self.transition_transparency >= 10:
                # Done transitioning
                self.transition = False
                if self.talk and self.owner in self.unit_sprites:
                    self.unit_sprites[self.owner].talk()
        elif self.scroll_y > 0:
            self.scroll_y -= 2
        else:
            num_updates = Engine.get_delta() / float(cf.OPTIONS['Text Speed']*self.slow_flag)
            self.total_num_updates += num_updates
            if self.total_num_updates >= 1:
                # handle the waiting markxzer's vertical offset
                self.waiting_cursor_offset_index += 1
                if self.waiting_cursor_offset_index > len(self.waiting_cursor_offset) - 1:
                    self.waiting_cursor_offset_index = 0

            while self.total_num_updates >= 1:
                self.total_num_updates -= 1
                if self._next_char():
                    self.total_num_updates = 0
                    break

    def draw(self, surf, unit_sprites):
        text_surf = self.text_surf.copy()
        if self.transition:
            scroll = 0
        else:
            end_text_position = self.draw_text(text_surf, (0, 0))
            scroll = int(self.main_font.height*self.scroll_help - self.scroll_y)
        text_surf = Engine.subsurface(text_surf, (0, scroll, text_surf.get_width(), text_surf.get_height() - scroll))
            
        surf_pos = self.position
        if surf_pos == 'center':
            self.topleft = (GC.WINWIDTH/2 - self.dlog_box.get_width()/2, GC.WINHEIGHT/2 - self.dlog_box.get_height()/2)
        else:
            self.topleft = surf_pos

        if self.transition:
            dlog_box = Image_Modification.resize(self.dlog_box, (1, self.transition_transparency/20. + .5))
            dlog_box = Image_Modification.flickerImageTranslucent(dlog_box, 100 - self.transition_transparency*10)
            topleft = self.topleft[0], self.topleft[1] + self.dlog_box.get_height() - dlog_box.get_height()
        else:
            dlog_box = self.dlog_box
            topleft = self.topleft
        surf.blit(dlog_box, topleft)

        if not self.transition:
            if self.message_tail:
                self.add_message_tail(surf, unit_sprites)
            self.add_nametag(surf, unit_sprites)
            surf.blit(text_surf, (self.topleft[0] + 8, self.topleft[1] + 8))

            # Handle the waiting cursor
            # print(end_text_position, scroll)
            if self.waiting and self.waiting_cursor_flag and not self.scroll_y:
                cursor_surf = self.waiting_cursor
                # Make sure we're not placing this where it shouldn't be
                if end_text_position[0] > text_surf.get_width() - self.wait_width:
                    # if end_text_position[1] != 0:
                    #    self._next_line() # Don't do this because it messes up the next line.
                    # print(self.num_lines*self.main_font.height)
                    if end_text_position[1] >= (self.num_lines-1)*self.main_font.height:
                        # self._next_line()
                        pass # For now
                    else:
                        end_text_position[0] = 0 # Move to next line
                        end_text_position[1] += self.main_font.height
                pos = (surf_pos[0] + 8 + end_text_position[0],
                       surf_pos[1] + 16 + end_text_position[1] + self.waiting_cursor_offset[self.waiting_cursor_offset_index])
                surf.blit(cursor_surf, pos)

class Credits(object):
    def __init__(self, title, text, title_font, font, wait=False, center=False):
        self.title = title
        self.text = text
        self.center = center
        self.wait = wait
        self.wait_flag = False
        self.title_font = GC.FONT[title_font]
        self.main_font = GC.FONT[font]

        self.populate_surface()

        self.position = [0, GC.WINHEIGHT]
        self.speed = 0.35

        self.last_update = Engine.get_time()

        # To match dialog
        self.waiting = False # Never waiting on unit input
        self.done = True # Always ready to move on to next dialog object
        self.hold = True # Always show, even if waiting
        self.solo_flag = False # As many of these on the screen as possible
        self.talk = False

    def populate_surface(self):
        index = 0
        self.parsed_text = []
        for credit in self.text:
            x_bound = GC.WINWIDTH - 12 if self.center else GC.WINWIDTH - 88
            l = MenuFunctions.line_wrap(MenuFunctions.line_chunk(credit), x_bound, self.main_font)
            for line in l:
                text = ''.join(line)
                if self.center:
                    x_pos = GC.WINWIDTH/2 - self.main_font.size(text)[0]/2
                else:
                    x_pos = 88
                y_pos = self.main_font.height*index + self.title_font.height
                index += 1
                self.parsed_text.append((text, index, (x_pos, y_pos)))

        self.length = index

        # Create surface
        size = GC.WINWIDTH, self.title_font.height + self.main_font.height * self.length
        self.surface = Engine.create_surface(size, transparent=True)

        # title - center?
        # title_pos_x = GC.WINWIDTH/2 - self.title_font.size(self.title)[0]/2
        title_pos_x = 32
        self.title_font.blit(self.title, self.surface, (title_pos_x, 0))

        # rest of text
        for text, index, pos in self.parsed_text:
            self.main_font.blit(text, self.surface, pos)

    def determine_wait(self):
        time = (self.length + 2) * self.main_font.height / self.speed * 1000/GC.FPS
        if self.wait:
            time += self.get_pause() * 2
        return time

    def get_pause(self):
        return -~self.length * 1000 # 1500

    def hurry_up(self):
        pass

    def is_done(self):
        return self.done

    def update(self):
        # print(self.position)
        current_time = Engine.get_time()
        if not self.wait_flag or current_time - self.last_update > self.get_pause():
            self.wait_flag = False
            self.position[1] -= self.speed
            self.last_update = current_time
            if self.wait and GC.WINHEIGHT/2 - self.surface.get_height()/2 >= self.position[1]:
                self.wait_flag = True
                self.wait = False

    def draw(self, surf, unit_sprites=None):
        self.update()
        surf.blit(self.surface, self.position)

class EndingsDisplay(object):
    def __init__(self, message_system, portrait1, title, text, stats, font='text_white'):
        self.message_system = message_system
        self.portrait = Engine.subsurface(GC.UNITDICT[portrait1 + 'Portrait'], (0, 0, 96, 80))
        self.title = title
        self.truetext = text
        self.text = []
        self.text_index = [0, 0] # Line num, character num within line
        self.font = GC.FONT[font]
        self.format_text(text)

        self.background = GC.IMAGESDICT['EndingsDisplay']
        self.populate_surface(portrait1, stats)

        self.x_position = GC.WINWIDTH

        self.wait_time = len(self.text) * 2000
        self.starting_update = Engine.get_time()
        self.last_update = 0

        # To match dialog
        self.waiting = False # Not waiting on unit input??
        self.done = False # Not always ready to move on to next dialog object
        self.hold = True # Always show, even if waiting
        self.solo_flag = True # Not as many of these as possible
        self.talk = False

    def populate_surface(self, name, stats):
        # Create surface
        size = GC.WINWIDTH, GC.WINHEIGHT
        self.surface = Engine.create_surface(size, transparent=True)

        self.surface.blit(self.background, (0, 0))
        self.surface.blit(self.portrait, (128 + 8, GC.WINHEIGHT - 80 - 24 + 1))

        title_pos_x = 68 - self.font.size(self.title)[0]/2
        self.font.blit(self.title, self.surface, (title_pos_x, 24))

        # === Stats
        # Get statistics
        kills, damage, healing = 0, 0, 0
        for level in stats:
            if name in level.stats:
                record = level.stats[name]
                kills += record['kills']
                damage += record['damage']
                healing += record['healing']

        # Print Statistics
        GC.FONT['text_yellow'].blit('K', self.surface, (136, 8))
        GC.FONT['text_yellow'].blit('D', self.surface, (168, 8))
        GC.FONT['text_yellow'].blit('H', self.surface, (200, 8))
        GC.FONT['text_blue'].blit(str(kills), self.surface, (144, 8))
        dam = str(damage)
        if damage >= 1000:
            dam = dam[:-3] + '.' + dam[-3] + 'k'
        heal = str(healing)
        if healing >= 1000:
            heal = heal[:-3] + '.' + dam[-3] + 'k'
        GC.FONT['text_blue'].blit(dam, self.surface, (176, 8))
        GC.FONT['text_blue'].blit(heal, self.surface, (208, 8))

    def format_text(self, text):
        l = MenuFunctions.line_wrap(MenuFunctions.line_chunk(text), GC.WINWIDTH - 32, self.font)
        for line in l:
            self.text.append(''.join(line))

    def hurry_up(self):
        self.text_index[0] = len(self.text)
        self.done = True
        self.waiting = True

    def is_done(self):
        return self.done

    def update(self):
        current_time = Engine.get_time()
        # Transition in
        if self.x_position > 0:
            self.x_position -= 8
            self.x_position = max(0, self.x_position)

        # Add text -- increment indices counter
        elif self.text_index[0] < len(self.text):
            if current_time - self.last_update > cf.OPTIONS['Text Speed']:
                self.last_update = current_time
                self.text_index[1] += 1
                if self.text_index[1] > len(self.text[self.text_index[0]]) - 1:
                    self.text_index[0] += 1
                    self.text_index[1] = 0

        else:
            self.done = True
            self.waiting = True
            # Only wait for so long
            if current_time - self.starting_update > self.wait_time:
                self.message_system.dialog_unpause() # Move further with process

    def draw(self, surf, unit_sprites=None):
        # self.update()
        surf.blit(self.surface, (self.x_position, 0))

        for index, line in enumerate(self.text[:self.text_index[0]+1]):
            if index == self.text_index[0]:
                new_line = line[:self.text_index[1]]
            else:
                new_line = line
            self.font.blit(new_line, surf, (self.x_position + 16, 48 + index*self.font.height))

class UnitPortrait(object):
    def __init__(self, portrait_name, blink_position, mouth_position, position, priority=0, mirror=False, transition=False, expression='Normal'):
        self.name = portrait_name
        try:
            portrait_sprite = GC.UNITDICT[portrait_name + 'Portrait'].copy()
        except KeyError:
            raise KeyError
        self.halfblink = Engine.subsurface(portrait_sprite, (96, 48, 32, 16))
        self.fullblink = Engine.subsurface(portrait_sprite, (96, 64, 32, 16))
        self.openmouth = Engine.subsurface(portrait_sprite, (0, 96, 32, 16))
        self.halfmouth = Engine.subsurface(portrait_sprite, (32, 96, 32, 16))
        self.closemouth = Engine.subsurface(portrait_sprite, (64, 96, 32, 16))
        self.opensmile = Engine.subsurface(portrait_sprite, (0, 80, 32, 16))
        self.halfsmile = Engine.subsurface(portrait_sprite, (32, 80, 32, 16))
        self.closesmile = Engine.subsurface(portrait_sprite, (64, 80, 32, 16))
        self.portrait = Engine.subsurface(portrait_sprite, (0, 0, 96, 80))

        self.blink_position = blink_position
        self.mouth_position = mouth_position

        self.position = position
        # For movement
        self.new_position = position
        self.isMoving = False
        self.last_move_update = 0
        self.unit_speed = 6
        self.update_time = 25

        self.priority = priority
        self.mirror = mirror
        self.remove_flag = False
        self.image = self.portrait.copy()

        # Talking setup
        self.talking = False
        self.talk_state = 0
        self.last_talk_update = 0
        self.next_talk_update = 0

        # Transition info
        self.transition = 'trans2color' if transition else 0
        self.transition_transparency = 100 if transition else 0  # 100 is most transparency
        self.transition_last_update = Engine.get_time()

        # Blinking set up
        self.blinking = 2 # 0- Don't blink, 1-hold blink, 2-Blink pseudorandomly
        self.offset_blinking = [x for x in range(-2000, 2000, 125)]
        self.blink_counter = Counters.generic3Counter(7000 + random.choice(self.offset_blinking), 40, 40) # 3 frames for each

        # Expression
        self.expression = expression

        # For bop
        self.bops_remaining = 0
        self.bop_state = False
        self.last_bop = None

    def talk(self):
        self.talking = True

    def stop_talking(self):
        self.talking = False

    def move(self, new_position):
        self.new_position = self.new_position[0] + new_position[0], self.new_position[1] + new_position[1]
        self.isMoving = True

    def bop(self):
        self.bops_remaining = 2
        self.bop_state = False
        self.last_bop = Engine.get_time()

    def update(self, current_time=None):
        if not current_time:
            current_time = Engine.get_time()
        # update mouth
        if self.talking and current_time - self.last_talk_update > self.next_talk_update:
            self.last_talk_update = current_time
            chance = random.randint(1, 10)
            if self.talk_state == 0:
                # 10% chance to skip to state 2    
                if chance == 1:
                    self.talk_state = 2
                    self.next_talk_update = random.randint(70, 160)
                else:
                    self.talk_state = 1
                    self.next_talk_update = random.randint(30, 50)
            elif self.talk_state == 1:
                # 10% chance to go back to state 0
                if chance == 1:
                    self.talk_state = 0
                    self.next_talk_update = random.randint(50, 100)
                else:
                    self.talk_state = 2
                    self.next_talk_update = random.randint(70, 160)
            elif self.talk_state == 2:
                # 10% chance to skip back to state 0
                # 10% chance to go back to state 1
                chance = random.randint(1, 10)
                if chance == 1:
                    self.talk_state = 0
                    self.next_talk_update = random.randint(50, 100)
                elif chance == 2:
                    self.talk_state = 1
                    self.next_talk_update = random.randint(30, 50)
                else:
                    self.talk_state = 3
                    self.next_talk_update = random.randint(30, 50)
            elif self.talk_state == 3:
                self.talk_state = 0
                self.next_talk_update = random.randint(50, 100)

        if not self.talking:
            self.talk_state = 0

        if self.blinking == 2:
            self.blink_counter.update(current_time)

        if self.transition:
            if self.transition == 'trans2color':
                self.transition_transparency = 100 - (current_time - self.transition_last_update)/2 # 12 frames
            elif self.transition == 'color2trans':
                self.transition_transparency = (current_time - self.transition_last_update)/2 # 12 frames
            if self.transition_transparency > 100 or self.transition_transparency < 0:
                self.transition = 0
                self.transition_transparency = max(0, min(100, self.transition_transparency))
                # Done transitioning to invisibility, so remove me!
                if self.remove_flag:
                    return True

        # Move unit if he/she needs to be moved
        if self.isMoving:
            if current_time - self.last_move_update > self.update_time:
                # Finds difference between new_position and position
                diff_pos = (self.new_position[0] - self.position[0], self.new_position[1] - self.position[1])
                # No longer moving if difference of position is small
                if diff_pos[0] <= self.unit_speed and diff_pos[0] >= -self.unit_speed and diff_pos[1] <= self.unit_speed and diff_pos[1] >= -self.unit_speed:
                    # Close enough for gov't work
                    self.position = self.new_position
                    self.isMoving = False
                else:
                    angle = math.atan2(diff_pos[1], diff_pos[0])
                    updated_position = (self.position[0] + self.unit_speed * math.cos(angle), self.position[1] + self.unit_speed * math.sin(angle))
                    self.position = updated_position

                self.last_move_update = current_time

        # Bop unit if he/she needs to be bopped
        if self.bops_remaining:
            if current_time - self.last_bop > 150:
                self.last_bop = current_time
                if self.bop_state:
                    self.position = self.position[0], self.position[1] - 2
                    self.bops_remaining -= 1
                else:
                    self.position = self.position[0], self.position[1] + 2
                self.bop_state = not self.bop_state
                
        return False

    def create_image(self):
        if self.blinking == 2:
            if self.blink_counter.count == 0:
                if self.expression == "Full_Blink":
                    self.image.blit(self.fullblink, self.blink_position)
                elif self.expression == "Half_Blink":
                    self.image.blit(self.halfblink, self.blink_position)
                else:
                    self.image = self.portrait.copy()
            elif self.blink_counter.count == 1:
                if self.expression == "Full_Blink":
                    self.image.blit(self.fullblink, self.blink_position)
                else:
                    self.image.blit(self.halfblink, self.blink_position)
            elif self.blink_counter.count == 2:
                self.image.blit(self.fullblink, self.blink_position)
        elif self.blinking == 1:
            self.image.blit(self.fullblink, self.blink_position)

        if self.talk_state == 0:
            if self.expression == "Smiling":
                self.image.blit(self.closesmile, self.mouth_position)
            else:
                self.image.blit(self.closemouth, self.mouth_position)
        elif self.talk_state == 1 or self.talk_state == 3:
            if self.expression == "Smiling":
                self.image.blit(self.halfsmile, self.mouth_position)
            else:
                self.image.blit(self.halfmouth, self.mouth_position)
        elif self.talk_state == 2:
            if self.expression == "Smiling":
                self.image.blit(self.opensmile, self.mouth_position) 
            else:
                self.image.blit(self.openmouth, self.mouth_position)
        
    def draw(self, surf):
        self.create_image()    
        # === MODS ===
        image_sprite = self.image.copy()

        if self.transition:
            image_sprite = Image_Modification.flickerImageBlackColorKey(image_sprite, self.transition_transparency)

        # Mirror if necessary...
        if self.mirror:
            image_sprite = Engine.flip_horiz(image_sprite)
        surf.blit(image_sprite, self.position)

    def remove(self):
        self.transition = 'color2trans'
        self.remove_flag = True
        self.transition_last_update = Engine.get_time()

#! usr/bin/env # Custom imports # === GET INFO FOR DIALOGUE SCENE ================================================== # Open this scene's file database # Get lines # Background sprite # Dictionary of scene sprites # Dialogs # Optional unit # logger.debug('locking %s', self.optionalunit) # logger.debug('locking %s', self.optionalunit2) # Tile Pos -- What tile is being acted on, if any (applied in unlock, switch, village, destroy, search, etc. scripts) # Next Position -- Saves a position # Whether we are processing # Whether we are done # Whether we are waiting for user input # For transition # No transition # 0 is transparent # Keeps track of where we are in the scene # Keeps track of how many ifs we've encountered while searching for # the bad ifs 'end'. # Keeps track of whether we've encountered a truth this level or not # Other options are "Waiting", "Transitioning" # Whether to enter the battle save state after this scene has completed # Whether to reset state to free state after this scene has completed # Whether to reset the boundary manager after this scene has completed. Set to true when tiles are changed # Handle waiting # Handles skipping # Handles unit face priority # Does this count as an event # Assumes all "if" statements evaluate to True? # Should be 0. # Reset transition # Doing some parsing of the lines # logger.debug(line) # if cf.OPTIONS['debug']: print(line[0], self.if_stack) # === CONDITIONALS PARSING # Whether we've encountered a truth this level # Impossible. Must have at least parsed an if before hitting an elif # If we haven't encountered a truth yet # if not self.if_stack[-1] and (len(self.if_stack) == 1 or all(t_value for t_value in self.if_stack[:-1])): # Impossible. Must have at least parsed an if before hitting an else # If the most recent is False but the rest below are non-existent or true # not self.if_stack[-1] and (len(self.if_stack) == 1 or all(t_value for t_value in self.if_stack[:-1])): # Parse stack stays the same # Done waiting. Head back to processing # Keep waiting. Do nothing # Stop talking when grabbing data - probably only affected at end of file # parse sceneLines # Increasing # I want 5 extra frames in black # Check if we're done # Don't dialog while transitioning or processing # Only update dialog if it exists, could also just be waiting or not even in this state # This runs the character generation... # logger.debug('Unlocking %s', self.optionalunit) # logger.debug('Unlocking %s', self.optionalunit2) # time = Engine.get_true_time() # === SKIPPING DIALOGUE # End skip # === PAUSE # === BACKGROUND # Change the background # Remove the background # === FOREGROUND # Change the foreground # === WORLD MAP # === UNIT SPRITE # Add a unit to the scene # This is a complicated method of parsing unit lines using 'u' as delimeter # Force wait after unit sprite is drawn to allow time to transition. # Add a unit to the scene without transition # Change a unit's expression # Remove a unit from the scene # Force wait after unit sprite is drawn to allow time to transition. # No transition plox # Change a units position # Remove line # Change the priority of a unit # Move a unit sprite # Force wait after unit sprite is moved to allow time to transition # Mirror the unit sprite # Bop the unit sprite up and down # === MUSIC # Fade in this musical accompanienment # === HANDLE ITEMS # Give the optional unit an item or give the unit named in the line the item # Find receiver # Append item to list of units items # Has the unit equip an item in their inventory if and only if that item is in their inventory by name # Give the gameStateObj GC.COLORDICT['gold']! # Add a skill/status to a unit # Find the unit this is supposed to go to # print(unit.name) # Give exp to a unit # === HANDLES UNITS ON MAP # Read input # Read input # Read input # Read input # Read input # === HANDLE CURSOR # If is a coordinate # Display Cursor 1 is yes, 0 is no # Force # === HANDLE OBJECTIVE # Using a flag instead of just going to battle save state because if I save while # there's a dialogue state on the stack, the dialogue has a surface which crashes the save # This problem might not exist anymore, but... # === HANDLE TILE CHANGES -- These get put in the command list # Change tile sprites. - command, pos, tile_sprite, size, transition # Add default transition # Ways of destroying. Defaults to instantaneous in command list # Change one tile # gameStateObj.boundary_manager.reset(gameStateObj) # Change area of tile (must include pic instead of id) # width, height = size # gameStateObj.boundary_manager.reset(gameStateObj) # Change one tile's information # Changing whole map tile data! # Changing whole map's sprites! # Reset whole map's tile_info! # Add weather # Remove weather # Add global status # Remove global status # Remove global status # Clear command list # === CLEANUP # force arrange # print(unit.name, unit.position) # Remove enemies # === GAME CONSTANTS # should be remembered for map # should be remembered for all game # === TRANSITIONS # Handle transition # Increasing # Decreasing # === CHANGING UNITS # === HANDLE TALKING # Add to dictionary # Add to dictionary # === DIALOGUE BOX # Add line of text # === CREDITS BOX # === ENDINGS BOX # === Pop dialog off top # === Show Victory Screen # === ROLL CREDITS # === Display Records # logger.info('Time taken: %s', Engine.get_true_time() - time) # Evaluate evals # Traverse backwards so we don't mess up the indexing # --- Quick fix # s, speaker, text -- Assume we forgot {w} auto # Stop processing # Split on breaks and clears # Now find out how big this needs to be to hold this # Clear current dialogue # Clear command # And do it at the end # Now find out how big this needs to be to hold this # Error checking # print('width', w) # This is an extra buffer to account for waiting cursor # If we got here, it was too bug # print('Text too big for dialog box!') # Wait a certain amount of time before next one # Stop processing # Draw if background exists # Blit Background # Update unit sprites -- if results in true, delete the unit sprite. -- faciliates 'r' command fade out # === SCENE SPRITES === # Blit sprites, sort them by their priority (ascending, so 2 is pasted after and on top of 1) # Draw text (Don't draw text while transitioning) # Removes waiting from dialog # Find unit # Determine where the unit will appear # If none, then use position in load # Using prev defined reinforcement positions # Using next position # If coord, use coord # If name, then we want to find a point adjacent to that characters position # Shuffle positions if necessary # Determine which positions I can't move onto # Now we have final pos # Find unit # Determine available positions to move to # Using prev defined reinforcement positions # Using next position # Using prev position # If coord, use coord # If name, then we want to find a point adjacent to that characters position # Shuffle positions if necessary # Determine which positions I can't move onto # Find unit # Determine where the unit will appear # Using prev defined reinforcement positions # If coord, use coord # If name, then we want to find a point adjacent to that characters position # Shuffle positions if necessary # Determine which positions I can't move onto # If we couldn't find a good position to place this unit, just place it anywhere # If we couldn't find a good position to place this unit, just push the dude out # If is a coordinate # === DIALOG CLASS ============================================================ # The actual text this dialog box will display # The position of the dialog box on its surf # who is saying this # Dictionary of all units on screen # Empty dictionary # A holding list for the individual characters that will be displayed # A holding list for each line that will be displayed # the max number of lines that may be displayed at a time # For scroll effect # Converts {} style constructs into commands. Adds truetext to text # Add first line # Background of text box # surface that text is drawn on # To match dialogue scene, so it can be used in the same place. # Only one of these on the screen at a time # Whether to print the characters out slower than normal # draw the next character # Wait! # Removes things in brackets from the word # print(word[::-1], letter, self.text_width) # test for special commands # if we've hit a line break command # go to next line # print(both_width) # if we've exceeded width # We've essentially done the next line break # we're waiting # print(letter, previous_lines, word[::-1], both_width, self.text_width) # if we've exceeded width # Add the letter to the most recent chunk # Get text for all chunks in this line # Can actually differ from above due to ligatures/digraphs. # Used for center # Ignore empty chunks, or else random spaces will appear # Do we flip the tail? # unit's x _ position < halfway point of screen - half length of unit sprite # On left side # Solve for x_position # If we wouldn't actually be on the dialogue box # while we haven't reached the end, process all the next chars... # 10 is max # 10 frames to load in # Done transitioning # handle the waiting markxzer's vertical offset # Handle the waiting cursor # print(end_text_position, scroll) # Make sure we're not placing this where it shouldn't be # if end_text_position[1] != 0: #    self._next_line() # Don't do this because it messes up the next line. # print(self.num_lines*self.main_font.height) # self._next_line() # For now # Move to next line # To match dialog # Never waiting on unit input # Always ready to move on to next dialog object # Always show, even if waiting # As many of these on the screen as possible # Create surface # title - center? # title_pos_x = GC.WINWIDTH/2 - self.title_font.size(self.title)[0]/2 # rest of text # 1500 # print(self.position) # Line num, character num within line # To match dialog # Not waiting on unit input?? # Not always ready to move on to next dialog object # Always show, even if waiting # Not as many of these as possible # Create surface # === Stats # Get statistics # Print Statistics # Transition in # Add text -- increment indices counter # Only wait for so long # Move further with process # self.update() # For movement # Talking setup # Transition info # 100 is most transparency # Blinking set up # 0- Don't blink, 1-hold blink, 2-Blink pseudorandomly # 3 frames for each # Expression # For bop # update mouth # 10% chance to skip to state 2 # 10% chance to go back to state 0 # 10% chance to skip back to state 0 # 10% chance to go back to state 1 # 12 frames # 12 frames # Done transitioning to invisibility, so remove me! # Move unit if he/she needs to be moved # Finds difference between new_position and position # No longer moving if difference of position is small # Close enough for gov't work # Bop unit if he/she needs to be bopped # === MODS === # Mirror if necessary...