settings {
    "main": {
        "description": "Escape from Junkertown!\nYour brilliant plan went wrong, and now you're trapped in Junkertown. Can you find the way out?\nCode: 4K66K\nv1.1 Made by EruIluvatar",
        "modeName": "Escape from Junkertown"
    },
    "lobby": {
        "allowPlayersInQueue": true,
        "team1Slots": 8,
        "team2Slots": 0
    },
    "gamemodes": {
        "escort": {
            "enabledMaps": [
                "junkertown"
            ],
            "enableHeroSwitching": false,
            "gamemodeStartTrigger": "immediately",
            "roleLimit": "2OfEachRolePerTeam",
            "payloadSpeed%": 500,
            "respawnTime%": 0,
            "spawnHealthPacks": "disabled"
        },
        "general": {
            "enableSkins": false
        }
    },
    "heroes": {
        "allTeams": {
            "ashe": {
                "enableInfiniteUlt": true
            },
            "junkrat": {
                "enablePrimaryFire": false,
                "enableAbility1": false,
                "enableAbility2": false,
                "enableMelee": false,
                "enableUlt": false
            },
            "roadhog": {
                "enablePrimaryFire": false,
                "enableSecondaryFire": false,
                "enableAbility1": false,
                "enableAbility2": false,
                "enableMelee": false,
                "enableUlt": false
            },
        }
    }
}



#!extension beamEffects
#!extension beamSounds
#!extension buffAndDebuffSounds
#!extension explosionSounds
#!extension kineticExplosionEffects
#!extension playMoreEffects
#!extension spawnMoreDummyBots

#!define PI 3.1415926535
#!define INF 100000000
#!define RACE_CONDITION_DELAY 0.1



enum Item:
    ID = 0, # unique item id (descriptive string)
    STATE, # state of the item (int)
    POINT, # location of the item (vect)
    RADIUS, # radius for item interaction (float)
    MESSAGE, # messages for item based on state (string array)
    ITEM_NAME, # name of received item if interacted with (string)
    PLAY_GET_SOUND, # whether to play interaction sound (bool)
    NEEDS_LOS, # whether los is needed to interact (bool)
    REMOVE_ON_STATE, # state at which the point is removed (int)
    # item use points use the fields below
    REQUIRED_ITEM, # item required to use with this point based on state (string array, matches with ITEM_NAME)
    SUCCESS_MESSAGE # message to play on successful item use based on state (string array)

# GLOBAL SETTINGS

#!define INTERACT_DISTANCE 4
#!define WALL_MESSAGE_TIMEOUT 3
#!define WALL_CHECK_TIME (0.016 * 4)
#!define TUTORIAL_GATE_OPEN_TIME 2
#!define TUTORIAL_GATE_OFFSET 0.52
#!define POWER_OFF_COLOR Color.RED
#!define POWER_BROKEN_COLOR Color.BLACK
#!define ALCOHOL_COST 60
#!define SMUGGLER_COST 100

#!define MOLE_PERSON_DEVICE_NAME "Anathema Device"

#!define BARN_NPC_HIDE_LOCATION vect(51, 9.15, -57)
#!define BRUCE_TEXT_LOCATION vect(-21.62, 6.48 + 1.6, -104.95)
#!define INSIDE_SMUGGLER_TEXT_LOCATION vect(-19.06, 13.48 + 1.6, -88.07)
#!define OUTSIDE_SMUGGLER_TEXT_LOCATION vect(-44.52, 4.49 + 1.6, -49.63)
#!define BARTENDER_TEXT_LOCATION vect(-54.65, 1.73 + 1.8, -131.02)
#!define SHADY_CHARACTER_TEXT_LOCATION vect(-35.02, 1.48 + 1.8, -126.33)

#!define DOOR_21_TEXT_LOCATION vect(-50.47, 13.63 + 0.6, -84.74)
#!define DOOR_22_TEXT_LOCATION vect(-54.41, 13.63 + 0.6, -84.09)
#!define DOOR_23_TEXT_LOCATION vect(-59.72, 13.63 + 0.6, -85.31)
#!define DOOR_24_TEXT_LOCATION vect(-48.46, 13.63 + 0.6, -90.79)
#!define DOOR_25_TEXT_LOCATION vect(-42.10, 14.63 + 0.6, -94.75)

#!define MOLE_PERSON_ROOM_TEXT_LOCATION vect(-51.97, 6.14 + 0.6, -3.03)
#!define MOLE_PERSON_OUTHOUSE_TEXT_LOCATION vect(31.32, 10.46, -48.16)
#!define MOLE_PERSON_CAR_TEXT_LOCATION vect(-12.12, 6.35, -33.15)
#!define MOLE_PERSON_CLIFF_TEXT_LOCATION vect(-2.96, 8.95, -100.44)

#!define OUTER_MINIGAME_BALL_SIZE 0.05



# GLOBAL VARIABLES

globalvar skipCutscenePlayers = []
globalvar introStage1Players = []
globalvar introStage2Players = []
globalvar tutorialPlayers = []
globalvar graphicsQualityPlayers = []
globalvar playersInDark = []
globalvar innerWirePlayers = []
globalvar powerPanelPlayers = [[], [], [], [], []]
globalvar graphicsWarningPlayers = []
globalvar innerMinigamePlayers = []
globalvar radioSmokePlayers = []
globalvar outerMinigamePlayers = []
globalvar barnHintPlayers = []
globalvar mapMarkerPlayers = []
globalvar barnMinigamePlayers = []
globalvar dvdLogoPlayers = []
globalvar satControlPlayers = []
globalvar insideMailTextPlayers = []
globalvar outsideMailTextPlayers = []

# tutorial text
enum TutorialText:
    INTERACT,
    INV_CYCLE,
    APPLY_ITEM,
    HINTS

globalvar tutorialTextPlayers = [[], [], [], [], []]

# equippables
enum Equip:
    HAS_ANY = 0,
    LITTLE_BUDDY,
    SUNSCREEN,
    SUNGLASES,
    DISGUISE,
    WINGS

globalvar equipPlayers = [[], [], [], [], []]

# achievements
enum Achievement:
    HAS_ANY = 0,
    MOST_WANTED, # find all the posters
    INFRASIGHT, # find the sunglasses without using the map
    PUZZLE_MASTER, # finish the color minigame in 5 moves
    IN_A_HURRY, # click on Bruce 60 times before the minute is up
    MASTER_HACKER, # look through the binoculars
    ESCAPE_ARTIST, # escape Junkertown
    END_MARKER # always at the end, the number of total achievements

# cutscenes
enum Cutscene:
    NONE = 0,
    INTRO,
    WAKING,
    INNER_RADIO,
    COMEDY,
    BRUCE_CONTACT,
    MOLE_LEADER_INTERACTION,
    MOLE_AGENT_INTERACTION,
    ENDING

# interaction point ids
enum Id:
    # item apply points
    tutGate1,
    tutGate2,
    innerWires,
    innerBarrel2,
    innerGenerator,
    innerRadio,
    innerWorkbench,
    innerToolbox2,
    outerWorkbench,
    outerBruce,
    outerSmuggler,
    outerBartender,
    outerShadyCharacter,
    outerDoor21,
    outerDoor22,
    outerDoor23,
    outerDoor24,
    outerDoor25,
    barnWorkbench,
    outsideHose,
    outsideSatControl,
    outsideSmuggler,
    outsideMolePersonRoom,
    outsideMolePersonOuthouse,
    outsideMolePersonCar,
    outsideMolePersonCliff,

    # interaction points
    # tutorial room
    tutSafe1,
    tutSafe2,
    tutSafe3,
    tutSafe4,
    tutSafe5,
    tutGold,
    tutBarrel1,
    tutBarrel2,
    tutBarrel3,
    tutBarrel4,
    tutBarrel5,
    tutBarrel6,
    tutBarrel7,
    # lights on section
    innerFridge,
    innerChest1,
    innerLocker1,
    innerLocker2,
    innerLocker3,
    innerLocker4,
    innerLocker5,
    innerPowerDistributor,
    innerBarrel1,
    innerBarrel3,
    innerNotBarrel,
    innerSwitch1,
    innerSwitch2,
    innerSwitch3,
    innerSwitch4,
    innerSwitch5,
    innerPowerBox,
    # larger inner section
    innerThrone,
    innerChest2,
    innerToolbox1,
    innerSmallToolbox1,
    innerTireRack,
    innerBrokenMech1,
    innerBrokenMech2,
    innerMech1,
    innerMech2,
    innerMinigameControl1,
    innerMinigameControl2,
    innerMinigameSkip,
    innerSunglasses,
    innerValve1,
    innerValve2,
    innerValve3,
    innerVent1,
    innerVent2,
    innerVent3,
    innerVent4,
    innerVent5,
    innerVent6,
    innerVent7,
    # courtyard area
    outerIceBox,
    outerSmallToolbox1,
    outerToolbox1,
    outerToolbox2,
    outerToolbox3,
    outerRadio,
    outerMic,
    outerWaterBarrel,
    outerCart,
    outerPoster1,
    outerPoster2,
    outerPoster3,
    outerPoster4,
    outerPoster5,
    outerPoster6,
    outerPoster7,
    outerPoster8,
    outerPoster9,
    outerPoster10,
    outerPosterOob11,
    outerPosterOob12,
    outerPosterOob13,
    outerPosterOob14,
    outerPosterOob15,
    outerBinoculars,
    outerMinigameDot1,
    outerMinigameDot2,
    outerMinigameDot3,
    outerMinigameDot4,
    outerMinigameDot5,
    outerMinigameDot6,
    outerMinigameDot7,
    outerMinigameDot8,
    outerMinigameDot9,
    outerMinigameDot10,
    outerMinigameRotate1,
    outerMinigameRotate2,
    outerMinigameRotate3,
    outerMinigameSkip,
    # barn area
    barnRadio,
    barnTvPanel,
    barnPachimari,
    barnFuelMachine,
    barnFridge,
    barnTireRack,
    barnPlan,
    barnToolbox1,
    barnToolbox2,
    barnSmallToolbox1,
    barnSafe1,
    barnSafe2,
    barnSafe3,
    barnEmptyShelf,
    barnPaperClue1,
    barnPaperClue2,
    barnBike,
    barnSwitch1,
    barnSwitch2,
    barnSwitch3,
    barnSwitch4,
    barnSwitch5,
    barnCodePanel,
    barnMinigameDot0,
    barnMinigameDot1,
    barnMinigameDot2,
    barnMinigameDot3,
    barnMinigameDot4,
    barnMinigameDot5,
    barnMinigameDot6,
    barnMinigameDot7,
    barnMinigameDot8,
    barnMinigameSkip,
    # larger outside area
    outsideJunkWorkbench,
    outsideSafe,
    outsideIceBox,
    outsideFridge1,
    outsideFridge2,
    outsideFridge3,
    outsideFridge4,
    outsideToolbox1,
    outsideToolbox2,
    outsideSmallToolbox,
    outsideChest1,
    outsideChest2,
    outsideChest3,
    outsideChest4,
    outsideChest5,
    outsidePoster1,
    outsidePoster2

globalvar achievementPlayers = [[], [], [], [], [], [], []]

# NPCs

globalvar shadyCharacter
globalvar bruceNpc
globalvar outsideSmugglerNpc
globalvar insideSmugglerNpc
globalvar bartenderNpc
globalvar barnNpc
globalvar cartNpc

# other
globalvar timer = 0
globalvar outerMinigameColors = [Color.RED, Color.BLACK]
globalvar moleCoinRiddle = [
    "Retrieve the gold device...",
    "from where we cannot go...",
    "the horde of golden ice...",
    "then secrets we'll bestow."
]

globalvar moleLeaderRiddle = [
    "You hold the treasure in your hand...",
    "from where we cannot go...",
    "Now show it to our leader and...",
    "prepare the rest to know.", # change

    # "The door to seek is one of these",
    # "but not the first or last", "and ends with digit odd", "but sums to more than four", "but digits don't repeat",
    # "the digits don't repeat themselves", "it's neither square nor triangle shaped",
    # "and has no place to wipe your feet", "and has no mat upon the floor"
    "The door to seek is one of these...",
    "but digits don't repeat...",
    "it's neither square nor triangle shaped...",
    "and has no place to wipe your feet."
]

# PLAYER VARIABLES

playervar effectDeleteQueue = []
playervar textDeleteQueue = []

# General temp variables
playervar tIndex
playervar tValue
playervar tStart
playervar tEnd
playervar tPos
playervar tText
playervar tMinigame
playervar tDebugHash
playervar tDebugIndex
playervar tDebugPos

# Player state
playervar pauseInteraction
playervar junkPos
playervar junkFacing
playervar hogPos
playervar hogFacing
playervar isJunk
playervar isHog
playervar junkInventory
playervar hogInventory
playervar inventory
playervar inventoryPlayer
playervar junkEquipped
playervar hogEquipped
playervar equipped
playervar insideSmugglerItem
playervar outsideSmugglerItem
playervar money
playervar achievements

# Game state
playervar timeStarted

playervar tutorialFinished
playervar wiresConnected
playervar generatorOn
playervar powerSwitched
playervar lightsOn

playervar isDoingInnerMinigame
playervar isHoldingInnerMinigameControl1
playervar isHoldingInnerMinigameControl2
playervar innerMinigameValue1
playervar innerMinigameValue2
playervar hasFinishedInnerMinigame

playervar hasFoundInnerRadio
playervar canBeTheOtherGuy

playervar numVentsClosed
playervar hasSunglasses
playervar hasSunscreen
playervar canGoOutside

playervar barnCode
playervar barnCodeInputted
playervar canLeaveBarn
playervar canAccessBarnSecret

playervar isDoingOuterMinigame
playervar outerMinigameRotState1
playervar outerMinigameRotState2
playervar outerMinigameRotState3
playervar outerMinigameColorState
playervar outerMinigamePosState
playervar hasFinishedOuterMinigame

playervar canUtilizeSmuggler

playervar isDoingBarnMinigame
playervar barnMinigameColorState
playervar barnMinigamePosState
playervar hasFinishedBarnMinigame

playervar molePersonRiddleLine
playervar molePersonInteractStage

playervar bruceTimer

playervar hasFinishedGame
playervar isOnSecondRun = false

playervar isDoingHint
playervar hintText

# achievements
playervar postersFound
playervar outsideMapFound
playervar outerMinigameMovesMade
playervar timesBruceClicked

# Item points

playervar itemPoints
playervar itemIds

# Item apply points

playervar tutGate1
playervar tutGate2

playervar innerWires
playervar innerBarrel2
playervar innerGenerator
playervar innerRadio
playervar innerWorkbench
playervar innerToolbox2

playervar outerBruce
playervar outerSmuggler
playervar outerBartender
playervar outerShadyCharacter
playervar outerWorkbench
playervar outerDoor21
playervar outerDoor22
playervar outerDoor23
playervar outerDoor24
playervar outerDoor25

playervar barnWorkbench

playervar outsideHose
playervar outsideSatControl
playervar outsideSmuggler
playervar outsideMolePersonRoom
playervar outsideMolePersonOuthouse
playervar outsideMolePersonCar
playervar outsideMolePersonCliff

# Other
playervar tutorialGate1End
playervar tutorialGate2End

playervar skipAlpha
playervar currentCutscene
playervar isCutsceneFinished

playervar bezier
playervar bezierDuration



# UTILITIES

# 2d arrays can't be modified directly, so we have to pull out a row, modify it, then reinsert it

# get an item entry by id
#!define GET_ITEM_ENTRY(id) [elem for elem in eventPlayer.itemPoints if elem[Item.ID] == id][0]

# save an item entry
#!define SAVE_STATE(item) eventPlayer.tIndex = eventPlayer.itemIds.index(item[Item.ID]) \
eventPlayer.itemPoints[eventPlayer.tIndex] = item

# distance between two vectors ignoring the height component
#!define DIST_2D(v1, v2) distance(vect((v1).x, 0, (v1).z), vect((v2).x, 0, (v2).z))

# distance from pos to the vertical plane defined by start and end vectors
#!define DIST_TO_WALL(start, end, pos) dotProduct(normalize(vect(-((end) - (start)).z, 0, ((end) - (start)).x)), pos - start)

# MAKE_WALL creates a vertical wall from start to end, pushing 90 degrees clockwise to the (end - start) vector
# when hit, displays the message, but with a timeout of WALL_MESSAGE_TIMEOUT
# it's an instantaneous check, so must be rerun every tick
playervar isShowingWallMessage = false

def doWallMessageTimeout():
    eventPlayer.isShowingWallMessage = true
    wait(WALL_MESSAGE_TIMEOUT)
    eventPlayer.isShowingWallMessage = false

#!define MAKE_WALL(start, end, message) if DIST_2D(((start) + (end)) / 2, eventPlayer.getPosition()) < distance(start, end) / 2 and eventPlayer.getPosition().y >= start.y and eventPlayer.getPosition().y <= end.y and DIST_TO_WALL(start, end, eventPlayer.getPosition()) < 0.9: \
    eventPlayer.setMoveSpeed(0) \
    eventPlayer.teleport(eventPlayer.getPosition() + normalize(vect(-((end) - (start)).z, 0, ((end) - (start)).x)) * (1 - DIST_TO_WALL(start, end, eventPlayer.getPosition()))) \
    if eventPlayer.isShowingWallMessage == false: \
        smallMessage(eventPlayer, message) \
        async(doWallMessageTimeout(), AsyncBehavior.NOOP) \
    eventPlayer.setMoveSpeed(100)

#!define MAKE_DAMAGE_WALL(start, end, message) if DIST_2D(((start) + (end)) / 2, eventPlayer.getPosition()) < distance(start, end) / 2 and eventPlayer.getPosition().y >= start.y and eventPlayer.getPosition().y <= end.y and DIST_TO_WALL(start, end, eventPlayer.getPosition()) < 1: \
    eventPlayer.clearStatusEffect(Status.INVINCIBLE) \
    damage(eventPlayer, eventPlayer, 50) \
    eventPlayer.setMoveSpeed(5) \
    eventPlayer.applyImpulse(vect(-((end) - (start)).z, 3, ((end) - (start)).x), 6, Relativity.TO_WORLD, Impulse.CANCEL_CONTRARY_MOTION) \
    wait(0.1) \
    eventPlayer.setStatusEffect(null, Status.INVINCIBLE, INF) \
    eventPlayer.setHealth(200) \
    eventPlayer.setMoveSpeed(100)

# Push and pop effects using the effect deletion queue
def pushEffectDeleteQueue():
    eventPlayer.effectDeleteQueue.append(getLastCreatedEntity())

def popEffectDeleteQueue():
    destroyEffect(eventPlayer.effectDeleteQueue[0])
    del eventPlayer.effectDeleteQueue[0]

def popAllEffectDeleteQueue():
    while len(eventPlayer.effectDeleteQueue) > 0:
        popEffectDeleteQueue()

# Push and pop text using the text deletion queue
def pushTextDeleteQueue():
    eventPlayer.textDeleteQueue.append(getLastCreatedText())

def popTextDeleteQueue():
    destroyInWorldText(eventPlayer.textDeleteQueue[0])
    del eventPlayer.textDeleteQueue[0]

def popAllTextDeleteQueue():
    while len(eventPlayer.textDeleteQueue) > 0:
        popTextDeleteQueue()

# Creates in world text for the event player
#!define CREATE_COLORED_TEXT(text, location, color) popTextDeleteQueue() \
createInWorldText(eventPlayer, text, location, 1, Clip.NONE, WorldTextReeval.NONE, color, SpecVisibility.NEVER) \
pushTextDeleteQueue()

#!define CREATE_TEXT(text, location) CREATE_COLORED_TEXT(text, location, Color.WHITE)

# Moves playervars X, Y, and Z along the bezier defined by bezier[0:4] for the duration in bezierDuration
def doBezier():
    eventPlayer.A = eventPlayer.bezier[0]
    eventPlayer.B = eventPlayer.bezier[1]
    eventPlayer.C = eventPlayer.bezier[2]
    eventPlayer.D = eventPlayer.bezier[3]

    eventPlayer.X = eventPlayer.A
    eventPlayer.Y = eventPlayer.B
    eventPlayer.Z = eventPlayer.C
    eventPlayer.T = 0

    chase(eventPlayer.X, eventPlayer.B, duration=eventPlayer.bezierDuration, ChaseReeval.DESTINATION_AND_DURATION)
    chase(eventPlayer.Y, eventPlayer.C, duration=eventPlayer.bezierDuration, ChaseReeval.DESTINATION_AND_DURATION)
    chase(eventPlayer.Z, eventPlayer.D, duration=eventPlayer.bezierDuration, ChaseReeval.DESTINATION_AND_DURATION)
    chase(eventPlayer.T, 1, duration=eventPlayer.bezierDuration, ChaseReeval.DESTINATION_AND_DURATION)

# Does bezier for all entries in eventPlayer.bezier
# is destructive for eventPlayer.bezier
def doBezierArray():
    while len(eventPlayer.bezier) >= 4:
        doBezier()
        wait(eventPlayer.bezierDuration, Wait.ABORT_WHEN_FALSE)
        del eventPlayer.bezier[0]
        del eventPlayer.bezier[0]
        del eventPlayer.bezier[0]

#!define BEZIER_POSITION ((eventPlayer.X + (eventPlayer.Y - eventPlayer.X) * eventPlayer.T) + ((eventPlayer.Y + (eventPlayer.Z - eventPlayer.Y) * eventPlayer.T) - (eventPlayer.X + (eventPlayer.Y - eventPlayer.X) * eventPlayer.T)) * eventPlayer.T)
#!define BEZIER_FACING (eventPlayer.Y + (eventPlayer.Z - eventPlayer.Y) * eventPlayer.T)

# Gives the player an item
#!define GIVE_ITEM(item) eventPlayer.inventory = item.concat(eventPlayer.inventory)

# Used to turn the whole screen black (doesn't actually fade)
#!define BLACK_SCREEN_POS vect(34, 6, -88)
#!define BLACK_SCREEN_FACING vect(0.01, -1, 0)
#!define FADE_TO_BLACK() eventPlayer.startCamera(BLACK_SCREEN_POS, BLACK_SCREEN_POS + BLACK_SCREEN_FACING, 0)

# Create text while faded to black
#!define LINE_SPACING 0.08
#!define CREATE_BLACK_TEXT(text, line) createInWorldText(eventPlayer, text, BLACK_SCREEN_POS + 2 * BLACK_SCREEN_FACING - vect(LINE_SPACING * (line), 0, 0), 1, Clip.NONE, WorldTextReeval.NONE, Color.WHITE, SpecVisibility.NEVER) \
pushTextDeleteQueue()

# Create text while faded to black that reevaluates
#!define CREATE_BLACK_TEXT_REEVAL(text, line) createInWorldText(eventPlayer, text, BLACK_SCREEN_POS + 2 * BLACK_SCREEN_FACING - vect(LINE_SPACING * (line), 0, 0), 1, Clip.NONE, WorldTextReeval.VISIBILITY_AND_STRING, Color.WHITE, SpecVisibility.NEVER) \
pushTextDeleteQueue()

# Creates text that can be scrolled using eventPlayer.tPos
#!define CREATE_MOVABLE_TEXT(text, line, size, color) createInWorldText(eventPlayer, text, BLACK_SCREEN_POS + 2 * BLACK_SCREEN_FACING - vect(LINE_SPACING * (line) - eventPlayer.tPos, 0, 0), size, Clip.NONE, WorldTextReeval.VISIBILITY_POSITION_AND_STRING, color, SpecVisibility.NEVER) \
pushTextDeleteQueue()

# Increments the state of the item with the given id
#!define INCREMENT_STATE(id) eventPlayer.tValue = GET_ITEM_ENTRY(id) \
eventPlayer.tValue[Item.STATE]++ \
SAVE_STATE(eventPlayer.tValue)

#!define ADD_TO_STATE(id, val) eventPlayer.tValue = GET_ITEM_ENTRY(id) \
eventPlayer.tValue[Item.STATE] += val \
SAVE_STATE(eventPlayer.tValue)

# Sets the state of the item with the given id
#!define SET_STATE(id, value) eventPlayer.tValue = GET_ITEM_ENTRY(id) \
eventPlayer.tValue[Item.STATE] = value \
SAVE_STATE(eventPlayer.tValue)

def hideEquips():
    equipPlayers[Equip.HAS_ANY].remove(eventPlayer)
    equipPlayers[Equip.LITTLE_BUDDY].remove(eventPlayer)
    equipPlayers[Equip.SUNSCREEN].remove(eventPlayer)
    equipPlayers[Equip.SUNGLASES].remove(eventPlayer)
    equipPlayers[Equip.DISGUISE].remove(eventPlayer)
    equipPlayers[Equip.WINGS].remove(eventPlayer)

def manageEquips():
    hideEquips()

    if len(eventPlayer.equipped) > 0:
        equipPlayers[Equip.HAS_ANY].append(eventPlayer)
    if Equip.LITTLE_BUDDY in eventPlayer.equipped:
        equipPlayers[Equip.LITTLE_BUDDY].append(eventPlayer)
    if Equip.SUNSCREEN in eventPlayer.equipped:
        equipPlayers[Equip.SUNSCREEN].append(eventPlayer)
    if Equip.SUNGLASES in eventPlayer.equipped:
        equipPlayers[Equip.SUNGLASES].append(eventPlayer)
    if Equip.DISGUISE in eventPlayer.equipped:
        equipPlayers[Equip.DISGUISE].append(eventPlayer)
    if Equip.WINGS in eventPlayer.equipped:
        equipPlayers[Equip.WINGS].append(eventPlayer)

def hideAchievements():
    achievementPlayers[Achievement.HAS_ANY].remove(eventPlayer)
    achievementPlayers[Achievement.MOST_WANTED].remove(eventPlayer)
    achievementPlayers[Achievement.INFRASIGHT].remove(eventPlayer)
    achievementPlayers[Achievement.PUZZLE_MASTER].remove(eventPlayer)
    achievementPlayers[Achievement.IN_A_HURRY].remove(eventPlayer)
    achievementPlayers[Achievement.MASTER_HACKER].remove(eventPlayer)
    achievementPlayers[Achievement.ESCAPE_ARTIST].remove(eventPlayer)

def manageAchievements():
    hideAchievements()

    if len(eventPlayer.achievements) > 0:
        achievementPlayers[Achievement.HAS_ANY].append(eventPlayer)
    if Achievement.MOST_WANTED in eventPlayer.achievements:
        achievementPlayers[Achievement.MOST_WANTED].append(eventPlayer)
    if Achievement.INFRASIGHT in eventPlayer.achievements:
        achievementPlayers[Achievement.INFRASIGHT].append(eventPlayer)
    if Achievement.PUZZLE_MASTER in eventPlayer.achievements:
        achievementPlayers[Achievement.PUZZLE_MASTER].append(eventPlayer)
    if Achievement.IN_A_HURRY in eventPlayer.achievements:
        achievementPlayers[Achievement.IN_A_HURRY].append(eventPlayer)
    if Achievement.MASTER_HACKER in eventPlayer.achievements:
        achievementPlayers[Achievement.MASTER_HACKER].append(eventPlayer)
    if Achievement.ESCAPE_ARTIST in eventPlayer.achievements:
        achievementPlayers[Achievement.ESCAPE_ARTIST].append(eventPlayer)

def hideUi():
    eventPlayer.disableHeroHUD()
    hideEquips()
    hideAchievements()
    eventPlayer.inventoryPlayer.remove(eventPlayer)

def showUi():
    eventPlayer.enableHeroHud()
    manageEquips()
    manageAchievements()
    eventPlayer.inventoryPlayer.append(eventPlayer)

# Freezes and unfreezes the player
def freeze():
    eventPlayer.pauseInteraction = true
    eventPlayer.setStatusEffect(null, Status.ROOTED, INF)

def unfreeze():
    eventPlayer.pauseInteraction = false
    eventPlayer.clearStatusEffect(Status.ROOTED)

# Opens the barn secret area
def openSecretDoor():
    barnNpc.teleport(vect(54.41, 9.5, -64.48))
    wait(0.5)
    barnNpc.teleport(BARN_NPC_HIDE_LOCATION)
    # cooldown
    wait(5)

# Toggles visiblity of the given power panel switch
#!define TOGGLE_POWER_SWITCH(index) if eventPlayer in powerPanelPlayers[index]: \
    powerPanelPlayers[index].remove(eventPlayer) \
else: \
    powerPanelPlayers[index].append(eventPlayer)

# Checks if the player has activated all the power switches
def checkPowerPanel():
    if all([eventPlayer not in elem for elem in powerPanelPlayers]):
        eventPlayer.powerSwitched = true

# Converts between 2D and 3D coordinate locations for the inner workbench minigame
#!define INNER_MINIGAME_X_0 vect(-103.33, 0, -183.70)
#!define INNER_MINIGAME_X_MAX vect(-103.65, 0, -185.43)
#!define INNER_MINIGAME_Y_0 8.73
#!define INNER_MINIGAME_2D_TO_3D(vect2d) INNER_MINIGAME_X_0 + (INNER_MINIGAME_X_MAX - INNER_MINIGAME_X_0) * vect2d.x / distance(INNER_MINIGAME_X_0, INNER_MINIGAME_X_MAX) + vect(0, vect2d.y + INNER_MINIGAME_Y_0, 0)
#!define INNER_MINIGAME_3D_TO_2D(vect3d) vect(distance(INNER_MINIGAME_X_0, vect(vect3d.x, 0, vect3d.z)), vect3d.y - INNER_MINIGAME_Y_0, 0)

# Converts between 2D and 3D coordinate locations for the outer workbench minigame
#!define OUTER_MINIGAME_X_0 vect(-56.627, 0, -61.328)
#!define OUTER_MINIGAME_X_MAX vect(-56.286, 0, -59.735)
#!define OUTER_MINIGAME_Y_0 11.998
#!define OUTER_MINIGAME_2D_TO_3D(vect2d) OUTER_MINIGAME_X_0 + (OUTER_MINIGAME_X_MAX - OUTER_MINIGAME_X_0) * vect2d.x / distance(OUTER_MINIGAME_X_0, OUTER_MINIGAME_X_MAX) + vect(0, vect2d.y + OUTER_MINIGAME_Y_0, 0)
#!define OUTER_MINIGAME_3D_TO_2D(vect3d) vect(distance(OUTER_MINIGAME_X_0, vect(vect3d.x, 0, vect3d.z)), vect3d.y - OUTER_MINIGAME_Y_0, 0)

# Toggle outer minigame state
#!define OUTER_MINIGAME_TOGGLE(num) eventPlayer.tPos = num \
outerMinigameToggle()

def outerMinigameToggle():
    eventPlayer.tMinigame = eventPlayer.outerMinigamePosState[eventPlayer.tPos]
    eventPlayer.outerMinigameColorState[eventPlayer.tMinigame]++
    eventPlayer.outerMinigameColorState[eventPlayer.tMinigame] %= 2

def outerMinigameClick():
    if eventPlayer.tPos == 1:
        chase(eventPlayer.outerMinigameRotState1, eventPlayer.outerMinigameRotState1 + 2 * PI / 3, duration=0.3, ChaseReeval.NONE)
        eventPlayer.tMinigame = eventPlayer.outerMinigamePosState[1]
        eventPlayer.outerMinigamePosState[1] = eventPlayer.outerMinigamePosState[2]
        eventPlayer.outerMinigamePosState[2] = eventPlayer.outerMinigamePosState[3]
        eventPlayer.outerMinigamePosState[3] = eventPlayer.tMinigame
    elif eventPlayer.tPos == 2:
        chase(eventPlayer.outerMinigameRotState2, eventPlayer.outerMinigameRotState2 + 2 * PI / 3, duration=0.3, ChaseReeval.NONE)
        eventPlayer.tMinigame = eventPlayer.outerMinigamePosState[4]
        eventPlayer.outerMinigamePosState[4] = eventPlayer.outerMinigamePosState[7]
        eventPlayer.outerMinigamePosState[7] = eventPlayer.outerMinigamePosState[8]
        eventPlayer.outerMinigamePosState[8] = eventPlayer.tMinigame
    else:
        chase(eventPlayer.outerMinigameRotState3, eventPlayer.outerMinigameRotState3 + 2 * PI / 3, duration=0.3, ChaseReeval.NONE)
        eventPlayer.tMinigame = eventPlayer.outerMinigamePosState[6]
        eventPlayer.outerMinigamePosState[6] = eventPlayer.outerMinigamePosState[9]
        eventPlayer.outerMinigamePosState[9] = eventPlayer.outerMinigamePosState[10]
        eventPlayer.outerMinigamePosState[10] = eventPlayer.tMinigame

    playEffect(eventPlayer, DynamicEffect.BRIGITTE_REPAIR_PACK_ARMOR_SOUND, null, eventPlayer, 50)
    wait(0.3)

# Converts between 2D and 3D coordinate locations for the barn workbench minigame
#!define BARN_MINIGAME_X_0 vect(48.95, 0, -80.60)
#!define BARN_MINIGAME_X_MAX vect(48.77, 0, -82.33)
#!define BARN_MINIGAME_Y_0 10.22
#!define BARN_MINIGAME_3D_TO_2D(vect3d) vect(distance(BARN_MINIGAME_X_0, vect(vect3d.x, 0, vect3d.z)), vect3d.y - BARN_MINIGAME_Y_0, 0)

#!define BARN_X_VEC normalize(BARN_MINIGAME_X_MAX - BARN_MINIGAME_X_0)
#!define BARN_Z_VEC crossProduct(BARN_X_VEC, vect(0, -1, 0))
#!define BARN_A vect(BARN_X_VEC.x, 0, BARN_Z_VEC.x)
#!define BARN_C vect(BARN_X_VEC.z, 0, BARN_Z_VEC.z)
#!define BARN_MINIGAME_2D_TO_3D(v) BARN_MINIGAME_X_0 + vect(dotProduct(BARN_A, v), BARN_MINIGAME_Y_0 + v.y, dotProduct(BARN_C, v))

# Barn minigame values
globalvar barnMinigamePoints = [
    vect(0.85, 0.1, 0.055),
    vect(0.6, 0.8, 0.055),
    vect(0.55, 0.6, 0.055),
    vect(0.8, 0.3, 0.055),
    vect(0.75, 0.5, 0.055),
    vect(1.1, 0.8, 0.055),
    vect(1.15, 0.6, 0.055),
    vect(0.9, 0.3, 0.055),
    vect(0.95, 0.5, 0.055),
    vect(-1000, -1000, 0)
]

globalvar barnMinigameConnections = [
    [3, 7],
    [2, 8],
    [1, 3, 4],
    [0, 2],
    [2, 5, 8],
    [4, 6],
    [5, 7, 8],
    [0, 6],
    [1, 4, 6]
]

# Barn minigame click helper
# eventPlayer.tPos should be set to the dot index to process
# player variables tIndex and tMinigame are overwritten
def barnMinigameClick():
    # tPos: index (in state array) of the spot that was clicked
    # tIndex: index (in pos array) of the effect at the point that was clicked
    # tMinigame: index (in state array) of the open spot to move to
    # tStart: chase variable for moving dot
    # tEnd: moving dot effect id

    eventPlayer.tIndex = eventPlayer.barnMinigamePosState.index(eventPlayer.tPos)
    if eventPlayer.tIndex >= 0:
        eventPlayer.tMinigame = [e for e in barnMinigameConnections[eventPlayer.tPos] if eventPlayer.barnMinigameColorState[e] == 0]
        if len(eventPlayer.tMinigame) > 0:
            eventPlayer.tStart = barnMinigamePoints[eventPlayer.barnMinigamePosState[eventPlayer.tIndex]]
            createEffect(eventPlayer, Effect.SPHERE, Color.BLACK if eventPlayer.barnMinigameColorState[eventPlayer.tPos] == 1 else Color.BLUE, BARN_MINIGAME_2D_TO_3D(eventPlayer.tStart), 0.05, EffectReeval.POSITION_AND_RADIUS)
            eventPlayer.tEnd = getLastCreatedEntity()
            createEffect(eventPlayer, Effect.JUNKRAT_TRAP_CHAIN_SOUND, null, eventPlayer, 30, EffectReeval.NONE)
            eventPlayer.tText = getLastCreatedEntity()
            wait(RACE_CONDITION_DELAY)
            eventPlayer.barnMinigamePosState[eventPlayer.tIndex] = 9
            chase(eventPlayer.tStart, barnMinigamePoints[eventPlayer.tMinigame[0]], duration=0.3, ChaseReeval.NONE)
            wait(0.3)
            destroyEffect(eventPlayer.tEnd)
            destroyEffect(eventPlayer.tText)
            stopChasingVariable(eventPlayer.tStart)

            eventPlayer.barnMinigamePosState[eventPlayer.tIndex] = eventPlayer.tMinigame[0]
            eventPlayer.barnMinigameColorState[eventPlayer.tMinigame[0]] = eventPlayer.barnMinigameColorState[eventPlayer.tPos]
            eventPlayer.barnMinigameColorState[eventPlayer.tPos] = 0
        else:
            playEffect(eventPlayer, DynamicEffect.EXPLOSION_SOUND, null, eventPlayer.getPosition(), 30)

# Converts between coords for the outside satellite control minigame
#!define OUTSIDE_SAT_CONTROL_ORIGIN vect(-4.106, 13.825, -83.328)
#!define OUTSIDE_SAT_TO_WORLD(pos) OUTSIDE_SAT_CONTROL_ORIGIN + vect(dotProduct(pos, vect(0.789, 0.120, -0.603)), dotProduct(pos, vect(-0.151, 0.989, 0)), dotProduct(pos, vect(-0.596, -0.091, -0.798)))

# Enters a digit to the barn code
#!define ENTER_BARN_DIGIT(digit) eventPlayer.barnCodeInputted.append(digit) \
if len(eventPlayer.barnCodeInputted) > 5: \
    del eventPlayer.barnCodeInputted[0]

# Checks if the barn code has been inputted
def checkBarnCode():
    if eventPlayer.barnCodeInputted == eventPlayer.barnCode:
        eventPlayer.canAccessBarnSecret = true
        barnHintPlayers.remove(eventPlayer)

        # Ashe drop
        async(openSecretDoor(), AsyncBehavior.NOOP)

# Makes a noise for when a switch is pressed
def makeSwitchNoise():
    createEffect(eventPlayer, Effect.ECHO_FOCUSING_BEAM_SOUND, null, eventPlayer, 100, EffectReeval.NONE)
    pushEffectDeleteQueue()
    wait(0.3)
    popEffectDeleteQueue()

# Toggle the lights when a button is pressed on the barn code panel
# the y value should be set in eventPlayer.tPos
def toggleBarnSwitch():
    eventPlayer.tStart = []
    createEffect(eventPlayer.tStart, Effect.SPHERE, Color.RED, vect(56.656, eventPlayer.tPos, -66.630), 0.013, EffectReeval.VISIBILITY)
    pushEffectDeleteQueue()
    createEffect(eventPlayer.tStart, Effect.SPHERE, Color.RED, vect(56.647, eventPlayer.tPos, -66.629), 0.013, EffectReeval.VISIBILITY)
    pushEffectDeleteQueue()
    createEffect(eventPlayer.tStart, Effect.SPHERE, Color.RED, vect(56.639, eventPlayer.tPos, -66.628), 0.013, EffectReeval.VISIBILITY)
    pushEffectDeleteQueue()
    createEffect(eventPlayer.tStart, Effect.SPHERE, Color.RED, vect(56.630, eventPlayer.tPos, -66.6275), 0.013, EffectReeval.VISIBILITY)
    pushEffectDeleteQueue()
    createEffect(eventPlayer.tStart, Effect.SPHERE, Color.RED, vect(56.622, eventPlayer.tPos, -66.627), 0.013, EffectReeval.VISIBILITY)
    pushEffectDeleteQueue()
    createEffect(eventPlayer.tStart, Effect.SPHERE, Color.RED, vect(56.613, eventPlayer.tPos, -66.626), 0.013, EffectReeval.VISIBILITY)
    pushEffectDeleteQueue()

    eventPlayer.tStart.append(eventPlayer)

    createEffect(eventPlayer, Effect.ECHO_FOCUSING_BEAM_SOUND, null, eventPlayer, 100, EffectReeval.NONE)
    pushEffectDeleteQueue()
    wait(0.3)

    eventPlayer.tStart.remove(eventPlayer)

    popEffectDeleteQueue()
    popEffectDeleteQueue()
    popEffectDeleteQueue()
    popEffectDeleteQueue()
    popEffectDeleteQueue()
    popEffectDeleteQueue()
    popEffectDeleteQueue()



# SETUP

rule "Disable hero assemble":
    @Event global
    @Condition isAssemblingHeroes()

    setMatchTime(0)

rule "Disable setup":
    @Event global
    @Condition isInSetup()

    setMatchTime(0)

rule "Move payload to the end":
    @Event global
    @Condition isGameInProgress()

    # wait for starting player to get through intro so the sound doesn't interfere
    wait(15)

    createDummy(Hero.REAPER, Team.2, -1, vect(29.09, 10.54, -81.34), vect(0.34, -0.77, 0.53))
    cartNpc = getLastCreatedEntity()
    cartNpc.startForcingName("Larry")

rule "Stop payload after first point":
    @Event global
    @Condition getPayloadProgressPercentage() > 38.20

    pauseMatchTime()

    cartNpc.teleport(BARN_NPC_HIDE_LOCATION)
    wait(15)
    cartNpc.teleport(vect(-45.75, 4.35, -73.84))
    cartNpc.setFacing(vect(-0.43, 0.39, -0.81), Relativity.TO_WORLD)
    cartNpc.startScalingSize(1.2, false)
    cartNpc.disableEnvironmentCollision(false)

rule "Keep match time high to prevent time based voice lines":
    @Event global
    @Condition isGameInProgress()
    @Condition getMatchTime() < 3000

    setMatchTime(3600)

rule "Spawn NPCs":
    @Event global
    @Condition isGameInProgress()

    # barn door opener
    createDummy(Hero.ZENYATTA, Team.2, -1, BARN_NPC_HIDE_LOCATION, vect(-0.98, 0, -0.07))
    barnNpc = getLastCreatedEntity()
    barnNpc.setInvisibility(Invis.ALL)
    barnNpc.disablePlayerCollision()
    barnNpc.disableEnvironmentCollision(false)

    # Bruce
    createDummy(Hero.TORBJORN, Team.2, -1, vect(-21.62, 6.48, -104.95), vect(0.92, 0, 0.39))
    bruceNpc = getLastCreatedEntity()
    bruceNpc.startForcingName("Bruce")
    bruceNpc.disableEnvironmentCollision(false)

    # smugglers
    createDummy(Hero.TRACER, Team.2, -1, vect(-19.06, 13.48, -88.07), vect(0.23, 0, -0.97))
    insideSmugglerNpc = getLastCreatedEntity()
    insideSmugglerNpc.startForcingName("Smuggler")
    insideSmugglerNpc.disableEnvironmentCollision(false)
    createDummy(Hero.TRACER, Team.2, -1, vect(-44.52, 4.49, -49.63), vect(0.54, 0, 0.84))
    outsideSmugglerNpc = getLastCreatedEntity()
    outsideSmugglerNpc.startForcingName("Smuggler")
    outsideSmugglerNpc.disableEnvironmentCollision(false)

    # bartender
    createDummy(Hero.ASHE, Team.2, -1, vect(-54.65, 1.73, -131.02), vect(0.87, 0, -0.49))
    bartenderNpc = getLastCreatedEntity()
    bartenderNpc.startForcingName("Bartender")
    bartenderNpc.disableEnvironmentCollision(false)

    # shady character
    createDummy(Hero.SOMBRA, Team.2, -1, vect(-35.02, 1.48, -126.33), vect(0.7, 0.10, -0.71))
    shadyCharacter = getLastCreatedEntity()
    shadyCharacter.startForcingName("Shady character")
    shadyCharacter.startScalingSize(1.1, false)
    shadyCharacter.disableEnvironmentCollision(false)

    # guards
    createDummy(Hero.REAPER, Team.2, -1, vect(-21.66, 4.49, -69.87), vect(-0.96, 0, 0.27))
    getLastCreatedEntity().startScalingSize(1.2, false)
    getLastCreatedEntity().disableEnvironmentCollision(false)
    getLastCreatedEntity().startForcingName("Moe")
    createDummy(Hero.REAPER, Team.2, -1, vect(-31.92, 12.48, -66.16), vect(0.45, 0, 0.89))
    getLastCreatedEntity().startScalingSize(1.2, false)
    getLastCreatedEntity().disableEnvironmentCollision(false)
    getLastCreatedEntity().startForcingName("Curly")

    getPlayers(Team.2).setStatusEffect(null, Status.INVINCIBLE, INF)

rule "Global setup":
    @Event global

    disableInspector()
    disableAnnouncer()
    disableGamemodeCompletion()
    setObjectiveDescription(getAllPlayers(), "Escape", HudReeval.VISIBILITY_AND_STRING)
    startForcingSpawn(Team.2, 2)
    getAllPlayers().disableNameplatesFor(getAllPlayers())

    chase(timer, 10000000, rate=1, ChaseReeval.NONE)

    # cutscene skip text
    hudSubtext(skipCutscenePlayers, "Hold {0} to skip".format(buttonString(Button.JUMP)), HudPosition.TOP, 10, rgba(127, 127, 127, localPlayer.skipAlpha), HudReeval.VISIBILITY_STRING_AND_COLOR, SpecVisibility.NEVER)

    # intro text
    createInWorldText(introStage1Players, "You had a plan", vect(45.98, 11.47, -71.48) + vect(-1, -0.01, 0.10), 50, Clip.SURFACES, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)
    createInWorldText(introStage1Players, "And now you're stuck", vect(-36.85, 6.07, -72.38) + vect(0.55, 0.12, 0.83), 50, Clip.SURFACES, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)
    createInWorldText(introStage2Players, "Escape", vect(-35.24, 13.01, -55.88), 50, Clip.SURFACES, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)
    createInWorldText(introStage2Players, "from", vect(-35.24, 12.51, -55.88), 50, Clip.SURFACES, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)

    # equipped hud text
    hudHeader(equipPlayers[Equip.HAS_ANY], "Equipped", HudPosition.LEFT, 1, Color.ORANGE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    hudHeader(equipPlayers[Equip.LITTLE_BUDDY], "{0} Little buddy".format(abilityIconString(Hero.ROADHOG, Button.ULTIMATE)), HudPosition.LEFT, 2, Color.ORANGE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    hudHeader(equipPlayers[Equip.SUNSCREEN], "{0} Sunscreen".format(abilityIconString(Hero.ROADHOG, Button.ABILITY_2)), HudPosition.LEFT, 3, Color.ORANGE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    hudHeader(equipPlayers[Equip.SUNGLASES], "{0} Sunglasses".format(abilityIconString(Hero.WINSTON, Button.ULTIMATE)), HudPosition.LEFT, 4, Color.ORANGE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    hudHeader(equipPlayers[Equip.DISGUISE], "{0} Hilarious Costume".format(abilityIconString(Hero.LUCIO, Button.ULTIMATE)), HudPosition.LEFT, 5, Color.ORANGE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    hudHeader(equipPlayers[Equip.WINGS], "{0} Mechanical wings".format(abilityIconString(Hero.MERCY, Button.ULTIMATE)), HudPosition.LEFT, 6, Color.ORANGE, HudReeval.VISIBILITY, SpecVisibility.NEVER)

    # achievement hud text
    hudHeader(achievementPlayers[Achievement.HAS_ANY], "Achievements", HudPosition.RIGHT, 1, Color.PURPLE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    hudHeader(achievementPlayers[Achievement.MOST_WANTED], "Most wanted {0}".format(abilityIconString(Hero.MCCREE, Button.ULTIMATE)), HudPosition.RIGHT, 2, Color.PURPLE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    hudHeader(achievementPlayers[Achievement.INFRASIGHT], "Infrasight {0}".format(abilityIconString(Hero.WIDOWMAKER, Button.ULTIMATE)), HudPosition.RIGHT, 3, Color.PURPLE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    hudHeader(achievementPlayers[Achievement.PUZZLE_MASTER], "Puzzle master {0}".format(abilityIconString(Hero.LUCIO, Button.JUMP)), HudPosition.RIGHT, 4, Color.PURPLE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    hudHeader(achievementPlayers[Achievement.IN_A_HURRY], "In a hurry {0}".format(abilityIconString(Hero.SOLDIER, Button.ABILITY_1)), HudPosition.RIGHT, 5, Color.PURPLE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    hudHeader(achievementPlayers[Achievement.MASTER_HACKER], "Master hacker {0}".format(abilityIconString(Hero.SOMBRA, Button.SECONDARY_FIRE)), HudPosition.RIGHT, 6, Color.PURPLE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    hudHeader(achievementPlayers[Achievement.ESCAPE_ARTIST], "Escape artist {0}".format(abilityIconString(Hero.BAPTISTE, Button.ULTIMATE)), HudPosition.RIGHT, 7, Color.PURPLE, HudReeval.VISIBILITY, SpecVisibility.NEVER)

    createInWorldText(getAllPlayers(), "Enter", vect(-55.67, 9.57, -82.45), 1, Clip.SURFACES, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)
    createInWorldText(getAllPlayers(), "Enter", vect(-66.45, 11.56, -80.28), 1, Clip.SURFACES, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)

    # tutorial text
    hudSubtext(graphicsQualityPlayers, "Certain details are only visible with graphics quality medium or higher", HudPosition.TOP, 0, Color.ORANGE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    hudSubtext(graphicsQualityPlayers, "Change the setting under Settings > Video > Graphics Quality", HudPosition.TOP, 1, Color.ORANGE, HudReeval.VISIBILITY, SpecVisibility.NEVER)
    createInWorldText(tutorialTextPlayers[TutorialText.INTERACT], "Use {0} to interact with things".format(buttonString(Button.PRIMARY_FIRE)), vect(-101.13, 12.35, -80.42), 1, Clip.SURFACES, WorldTextReeval.VISIBILITY_AND_STRING, Color.WHITE, SpecVisibility.NEVER)
    createInWorldText(tutorialTextPlayers[TutorialText.INV_CYCLE], "Use {0} / {1} to cycle inventory items".format(buttonString(Button.ULTIMATE), buttonString(Button.ABILITY_2)), vect(-88.78, 12.51, -90.72), 1, Clip.SURFACES, WorldTextReeval.VISIBILITY_AND_STRING, Color.WHITE, SpecVisibility.NEVER)
    createInWorldText(tutorialTextPlayers[TutorialText.APPLY_ITEM], "Use {0} to use inventory items on things\n\n                                  {1}".format(buttonString(Button.SECONDARY_FIRE), iconString(Icon.ARROW_DOWN)), vect(-100.28, 13.26, -90.12), 1, Clip.SURFACES, WorldTextReeval.VISIBILITY_AND_STRING, Color.WHITE, SpecVisibility.NEVER)
    createInWorldText(tutorialTextPlayers[TutorialText.HINTS], "Use {0} to toggle the hint system".format(buttonString(Button.RELOAD)), vect(-112.74, 11.88, -100.54), 1, Clip.SURFACES, WorldTextReeval.VISIBILITY_AND_STRING, Color.WHITE, SpecVisibility.NEVER)

    # tutorial gate
    createBeam(tutorialPlayers, Beam.TORBJORN_TURRET_SIGHT, vect(-99.01, 10.93, -91.03), localPlayer.tutorialGate1End, Color.RED, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createBeam(tutorialPlayers, Beam.TORBJORN_TURRET_SIGHT, vect(-99.01, 10.93, -91.03) + vect(0, TUTORIAL_GATE_OFFSET, 0) * 1, localPlayer.tutorialGate1End + vect(0, TUTORIAL_GATE_OFFSET, 0) * 1, Color.RED, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createBeam(tutorialPlayers, Beam.TORBJORN_TURRET_SIGHT, vect(-99.01, 10.93, -91.03) + vect(0, TUTORIAL_GATE_OFFSET, 0) * 2, localPlayer.tutorialGate1End + vect(0, TUTORIAL_GATE_OFFSET, 0) * 2, Color.RED, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createBeam(tutorialPlayers, Beam.TORBJORN_TURRET_SIGHT, vect(-99.01, 10.93, -91.03) + vect(0, TUTORIAL_GATE_OFFSET, 0) * 3, localPlayer.tutorialGate1End + vect(0, TUTORIAL_GATE_OFFSET, 0) * 3, Color.RED, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createBeam(tutorialPlayers, Beam.TORBJORN_TURRET_SIGHT, vect(-99.01, 10.93, -91.03) + vect(0, TUTORIAL_GATE_OFFSET, 0) * 4, localPlayer.tutorialGate1End + vect(0, TUTORIAL_GATE_OFFSET, 0) * 4, Color.RED, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createBeam(tutorialPlayers, Beam.TORBJORN_TURRET_SIGHT, vect(-99.01, 10.93, -91.03) + vect(0, TUTORIAL_GATE_OFFSET, 0) * 5, localPlayer.tutorialGate1End + vect(0, TUTORIAL_GATE_OFFSET, 0) * 5, Color.RED, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)

    createBeam(tutorialPlayers, Beam.TORBJORN_TURRET_SIGHT, vect(-94.20, 10.93, -92.04), localPlayer.tutorialGate2End, Color.RED, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createBeam(tutorialPlayers, Beam.TORBJORN_TURRET_SIGHT, vect(-94.20, 10.93, -92.04) + vect(0, TUTORIAL_GATE_OFFSET, 0) * 1, localPlayer.tutorialGate2End + vect(0, TUTORIAL_GATE_OFFSET, 0) * 1, Color.RED, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createBeam(tutorialPlayers, Beam.TORBJORN_TURRET_SIGHT, vect(-94.20, 10.93, -92.04) + vect(0, TUTORIAL_GATE_OFFSET, 0) * 2, localPlayer.tutorialGate2End + vect(0, TUTORIAL_GATE_OFFSET, 0) * 2, Color.RED, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createBeam(tutorialPlayers, Beam.TORBJORN_TURRET_SIGHT, vect(-94.20, 10.93, -92.04) + vect(0, TUTORIAL_GATE_OFFSET, 0) * 3, localPlayer.tutorialGate2End + vect(0, TUTORIAL_GATE_OFFSET, 0) * 3, Color.RED, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createBeam(tutorialPlayers, Beam.TORBJORN_TURRET_SIGHT, vect(-94.20, 10.93, -92.04) + vect(0, TUTORIAL_GATE_OFFSET, 0) * 4, localPlayer.tutorialGate2End + vect(0, TUTORIAL_GATE_OFFSET, 0) * 4, Color.RED, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createBeam(tutorialPlayers, Beam.TORBJORN_TURRET_SIGHT, vect(-94.20, 10.93, -92.04) + vect(0, TUTORIAL_GATE_OFFSET, 0) * 5, localPlayer.tutorialGate2End + vect(0, TUTORIAL_GATE_OFFSET, 0) * 5, Color.RED, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)

    createEffect(tutorialPlayers, Effect.ZARYA_PARTICLE_BEAM_SOUND, null, localPlayer.getPosition(), (5 - distance(vect(-100.32, 11.44, -90.75), localPlayer.getPosition())) * 30, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createEffect(tutorialPlayers, Effect.ZARYA_PARTICLE_BEAM_SOUND, null, localPlayer.getPosition(), (5 - distance(vect(-92.97, 11.44, -92.29), localPlayer.getPosition())) * 30, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)

    # inner wire connection
    createBeam(innerWirePlayers, Beam.GRAPPLE, vect(-106.59, 12.46, -135.78), vect(-107.26, 12.50, -135.87), null, EffectReeval.VISIBILITY)

    # power panel
    createEffect(powerPanelPlayers[0], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.392, 14.145, -137.307), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[0], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.394, 14.145, -137.316), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[0], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.396, 14.145, -137.325), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[0], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.398, 14.145, -137.334), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[0], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.400, 14.145, -137.343), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[0], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.402, 14.145, -137.352), 0.013, EffectReeval.VISIBILITY)

    createEffect(powerPanelPlayers[1], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.392, 14.071, -137.307), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[1], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.394, 14.071, -137.316), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[1], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.396, 14.071, -137.325), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[1], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.398, 14.071, -137.334), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[1], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.400, 14.071, -137.343), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[1], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.402, 14.071, -137.352), 0.013, EffectReeval.VISIBILITY)

    createEffect(powerPanelPlayers[2], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.392, 13.998, -137.307), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[2], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.394, 13.998, -137.316), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[2], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.396, 13.998, -137.325), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[2], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.398, 13.998, -137.334), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[2], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.400, 13.998, -137.343), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[2], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.402, 13.998, -137.352), 0.013, EffectReeval.VISIBILITY)

    createEffect(getAllPlayers(), Effect.SPHERE, POWER_BROKEN_COLOR, vect(-111.392, 13.914, -137.307), 0.013, EffectReeval.VISIBILITY)
    createEffect(getAllPlayers(), Effect.SPHERE, POWER_BROKEN_COLOR, vect(-111.394, 13.914, -137.316), 0.013, EffectReeval.VISIBILITY)
    createEffect(getAllPlayers(), Effect.SPHERE, POWER_BROKEN_COLOR, vect(-111.396, 13.914, -137.325), 0.013, EffectReeval.VISIBILITY)
    createEffect(getAllPlayers(), Effect.SPHERE, POWER_BROKEN_COLOR, vect(-111.398, 13.914, -137.334), 0.013, EffectReeval.VISIBILITY)
    createEffect(getAllPlayers(), Effect.SPHERE, POWER_BROKEN_COLOR, vect(-111.400, 13.914, -137.343), 0.013, EffectReeval.VISIBILITY)
    createEffect(getAllPlayers(), Effect.SPHERE, POWER_BROKEN_COLOR, vect(-111.402, 13.914, -137.352), 0.013, EffectReeval.VISIBILITY)

    createEffect(powerPanelPlayers[4], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.392, 13.835, -137.307), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[4], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.394, 13.835, -137.316), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[4], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.396, 13.835, -137.325), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[4], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.398, 13.835, -137.334), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[4], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.400, 13.835, -137.343), 0.013, EffectReeval.VISIBILITY)
    createEffect(powerPanelPlayers[4], Effect.SPHERE, POWER_OFF_COLOR, vect(-111.402, 13.835, -137.352), 0.013, EffectReeval.VISIBILITY)

    # graphics warning
    createInWorldText(graphicsWarningPlayers, "If you can see this,\nyour graphics level\nis set to low.\n\nPlease set to\nmedium or higher.", vect(-111.67, 13.65, -137.37), 0.08, Clip.SURFACES, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)

    # window 0
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-80.91, 8.29, -97.13), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-80.91, 8.79, -97.13), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-80.91, 9.29, -97.13), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-80.91, 9.79, -97.13), 5, EffectReeval.VISIBILITY)

    # window 1
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-98.09, 8.11, -113.37), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-97.83, 8.11, -112.13), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-97.57, 8.11, -110.89), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-97.30, 8.11, -109.65), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-97.04, 8.11, -108.41), 5, EffectReeval.VISIBILITY)

    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-98.09, 9.89, -113.37), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-97.83, 9.89, -112.13), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-97.57, 9.89, -110.89), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-97.30, 9.89, -109.65), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-97.04, 9.89, -108.41), 5, EffectReeval.VISIBILITY)

    # window 2
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.94, 8.72, -120.05), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.74, 8.72, -119.11), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.54, 8.72, -118.17), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.34, 8.72, -117.23), 5, EffectReeval.VISIBILITY)

    # window 3
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-101.44, 8.11, -129.02), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-101.18, 8.11, -127.78), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-100.92, 8.11, -126.54), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-100.66, 8.11, -125.30), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-100.40, 8.11, -124.06), 5, EffectReeval.VISIBILITY)

    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-101.44, 9.89, -129.02), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-101.18, 9.89, -127.78), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-100.92, 9.89, -126.54), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-100.66, 9.89, -125.30), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-100.40, 9.89, -124.06), 5, EffectReeval.VISIBILITY)

    # window 4
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-100.18, 7.97, -138.75), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-100.03, 7.97, -138.04), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.88, 7.97, -137.33), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.73, 7.97, -136.62), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.58, 7.97, -135.91), 5, EffectReeval.VISIBILITY)

    # window 5
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-100.32, 14.01, -140.24), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-100.16, 14.01, -139.46), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-100.00, 14.01, -138.68), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.83, 14.01, -137.90), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.67, 14.01, -137.12), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.51, 14.01, -136.33), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.34, 14.01, -135.55), 5, EffectReeval.VISIBILITY)

    # window 6
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-97.36, 14.01, -151.78), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-97.79, 14.01, -151.12), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-98.23, 14.01, -150.45), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-98.67, 14.01, -149.78), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.11, 14.01, -149.11), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.54, 14.01, -148.44), 5, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-99.98, 14.01, -147.77), 5, EffectReeval.VISIBILITY)

    # window 7
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-92.30, 11, -169.23), 3, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-92.30, 11.5, -169.23), 3, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-92.30, 12, -169.23), 3, EffectReeval.VISIBILITY)
    createEffect(playersInDark, Effect.BAD_AURA, Color.BLACK, vect(-92.30, 12.5, -169.23), 3, EffectReeval.VISIBILITY)

    # inner workbench puzzle
    createEffect(innerMinigamePlayers, Effect.SPHERE, Color.GRAY, vect(-103.45, 8.89, -184.36), 0.04, EffectReeval.VISIBILITY)
    createEffect(innerMinigamePlayers, Effect.SPHERE, Color.GRAY, vect(-103.50, 8.89, -184.63), 0.04, EffectReeval.VISIBILITY)
    createBeam(innerMinigamePlayers, Beam.TORBJORN_TURRET_SIGHT, INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(3 * PI / 2) * 0.422 + sin(PI / 6) * 0.2, 0.451 + cos(PI / 6) * 0.2 + sin(3 * PI / 2 * 2) * 0.15, 0)), INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(3 * PI / 2) * 0.422 + sin(PI / 6 + 2 * PI / 3) * 0.2, 0.451 + cos(PI / 6 + 2 * PI / 3) * 0.2 + sin(3 * PI / 2 * 2) * 0.15, 0)), null, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createBeam(innerMinigamePlayers, Beam.TORBJORN_TURRET_SIGHT, INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(3 * PI / 2) * 0.422 + sin(PI / 6 + 2 * PI / 3) * 0.2, 0.451 + cos(PI / 6 + 2 * PI / 3) * 0.2 + sin(3 * PI / 2 * 2) * 0.15, 0)), INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(3 * PI / 2) * 0.422 + sin(PI / 6 + 4 * PI / 3) * 0.2, 0.451 + cos(PI / 6 + 4 * PI / 3) * 0.2 + sin(3 * PI / 2 * 2) * 0.15, 0)), null, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createBeam(innerMinigamePlayers, Beam.TORBJORN_TURRET_SIGHT, INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(3 * PI / 2) * 0.422 + sin(PI / 6 + 4 * PI / 3) * 0.2, 0.451 + cos(PI / 6 + 4 * PI / 3) * 0.2 + sin(3 * PI / 2 * 2) * 0.15, 0)), INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(3 * PI / 2) * 0.422 + sin(PI / 6) * 0.2, 0.451 + cos(PI / 6) * 0.2 + sin(3 * PI / 2 * 2) * 0.15, 0)), null, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    createEffect(innerMinigamePlayers, Effect.SPHERE, Color.BLACK, INNER_MINIGAME_2D_TO_3D(vect(1.48, 0.73, 0)), 0.05, EffectReeval.VISIBILITY)
    createInWorldText(innerMinigamePlayers, "skip", INNER_MINIGAME_2D_TO_3D(vect(1.48, 0.73 + 0.04, 0)), 1, Clip.NONE, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)
    createInWorldText(innerMinigamePlayers, "Use the gray dots to make the arrow point the other way", vect(-103.48, 9.64, -184.53), 1, Clip.NONE, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)

    # radio smoke effect
    createEffect(radioSmokePlayers, Effect.CLOUD, Color.BLACK, vect(-90.05, 8.69, -103.28), 0.1, EffectReeval.VISIBILITY)

    # outer workbench puzzle
    createEffect(outerMinigamePlayers, Effect.SPHERE, Color.GRAY, OUTER_MINIGAME_2D_TO_3D(vect(0.92, 0.08 + 0.17 / 3, 0)), 0.03, EffectReeval.VISIBILITY)
    createEffect(outerMinigamePlayers, Effect.SPHERE, Color.GRAY, OUTER_MINIGAME_2D_TO_3D(vect(0.52, 0.08 + 0.17 / 3, 0)), 0.03, EffectReeval.VISIBILITY)
    createEffect(outerMinigamePlayers, Effect.SPHERE, Color.GRAY, OUTER_MINIGAME_2D_TO_3D(vect(0.72, 0.08 + 0.17 * 7 / 3, 0)), 0.03, EffectReeval.VISIBILITY)
    createEffect(outerMinigamePlayers, Effect.SPHERE, Color.BLACK, OUTER_MINIGAME_2D_TO_3D(vect(1.15, 0.59, 0)), 0.05, EffectReeval.VISIBILITY)
    createInWorldText(outerMinigamePlayers, "skip", OUTER_MINIGAME_2D_TO_3D(vect(1.15, 0.59 + 0.04, 0)), 1, Clip.NONE, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)
    createInWorldText(outerMinigamePlayers, "Turn all the red dots black. Click the gray dots to rotate the surrounding ones.", vect(-56.46, 12.81, -60.53), 1, Clip.NONE, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)

    # map marker
    createEffect(mapMarkerPlayers, Effect.SPHERE, Color.RED, vect(-88.04, 13.37, -158.22), 0.5, EffectReeval.VISIBILITY)

    # barn paper clues
    createEffect(barnHintPlayers, Effect.SPARKLES, Color.YELLOW, vect(58.30, 8.3, -93.83), 0.3, EffectReeval.VISIBILITY)
    createEffect(barnHintPlayers, Effect.SPARKLES, Color.YELLOW, vect(42.25, 15.3, -71.88), 0.3, EffectReeval.VISIBILITY)

    # barn workbench puzzle
    for I in range(len(barnMinigameConnections)):
        for J in range(len(barnMinigameConnections[I])):
            if I < barnMinigameConnections[I][J]:
                createBeam(barnMinigamePlayers, Beam.GRAPPLE, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[I]), BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[barnMinigameConnections[I][J]]), null, EffectReeval.VISIBILITY)

    createEffect(barnMinigamePlayers, Effect.SPHERE, Color.BLACK, BARN_MINIGAME_2D_TO_3D(vect(1.4, 0.8, 0)), 0.05, EffectReeval.VISIBILITY)
    createInWorldText(barnMinigamePlayers, "skip", BARN_MINIGAME_2D_TO_3D(vect(1.4, 0.8 + 0.04, 0)), 1, Clip.NONE, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)
    createInWorldText(barnMinigamePlayers, "Swap the positions of the black and white dots", vect(48.86, 11.14, -81.48), 1, Clip.NONE, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)

    # sat control
    createEffect(satControlPlayers, Effect.SPHERE, Color.BLUE, OUTSIDE_SAT_TO_WORLD(vect(0.1, 0.1, 0)), 0.05, EffectReeval.VISIBILITY)
    createEffect(satControlPlayers, Effect.SPHERE, Color.BLUE, OUTSIDE_SAT_TO_WORLD(vect(1.7, 0.1, 0)), 0.05, EffectReeval.VISIBILITY)
    createEffect(satControlPlayers, Effect.SPHERE, Color.BLUE, OUTSIDE_SAT_TO_WORLD(vect(0.1, 0.9, 0)), 0.05, EffectReeval.VISIBILITY)
    createEffect(satControlPlayers, Effect.SPHERE, Color.BLUE, OUTSIDE_SAT_TO_WORLD(vect(1.7, 0.9, 0)), 0.05, EffectReeval.VISIBILITY)

    # smuggler mail txt
    createInWorldText(insideMailTextPlayers, "You've got mail!", INSIDE_SMUGGLER_TEXT_LOCATION - vect(0, 0.1, 0), 1, Clip.SURFACES, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)
    createInWorldText(outsideMailTextPlayers, "You've got mail!", OUTSIDE_SMUGGLER_TEXT_LOCATION - vect(0, 0.1, 0), 1, Clip.SURFACES, WorldTextReeval.VISIBILITY, Color.WHITE, SpecVisibility.NEVER)

def innerMinigameInitialState():
    eventPlayer.isDoingInnerMinigame = false
    eventPlayer.isHoldingInnerMinigameControl1 = false
    eventPlayer.isHoldingInnerMinigameControl2 = false
    stopChasingVariable(eventPlayer.innerMinigameValue1)
    stopChasingVariable(eventPlayer.innerMinigameValue2)
    eventPlayer.innerMinigameValue1 = PI / 2
    eventPlayer.innerMinigameValue2 = PI / 2

def outerMinigameInitialState():
    eventPlayer.isDoingOuterMinigame = false
    stopChasingVariable(eventPlayer.outerMinigameRotState1)
    stopChasingVariable(eventPlayer.outerMinigameRotState2)
    stopChasingVariable(eventPlayer.outerMinigameRotState3)
    eventPlayer.outerMinigameRotState1 = 0
    eventPlayer.outerMinigameRotState2 = 0
    eventPlayer.outerMinigameRotState3 = 0
    eventPlayer.outerMinigameColorState = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    eventPlayer.outerMinigamePosState = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    eventPlayer.outerMinigameMovesMade = 0

def barnMinigameInitialState():
    eventPlayer.isDoingBarnMinigame = false
    eventPlayer.barnMinigameColorState = [0, 1, 1, 1, 1, 2, 2, 2, 2]
    eventPlayer.barnMinigamePosState = [1, 2, 3, 4, 5, 6, 7, 8]

def playerInitialState():
    # Stop chasing
    stopChasingVariable(eventPlayer.X)
    stopChasingVariable(eventPlayer.Y)
    stopChasingVariable(eventPlayer.Z)
    stopChasingVariable(eventPlayer.T)
    stopChasingVariable(eventPlayer.tStart)
    stopChasingVariable(eventPlayer.tEnd)
    stopChasingVariable(eventPlayer.tPos)
    stopChasingVariable(eventPlayer.innerMinigameValue1)
    stopChasingVariable(eventPlayer.innerMinigameValue2)
    stopChasingVariable(eventPlayer.bruceTimer)
    stopChasingVariable(eventPlayer.tutorialGate1End)
    stopChasingVariable(eventPlayer.tutorialGate2End)
    stopChasingVariable(eventPlayer.skipAlpha)

    eventPlayer.stopFacing()
    eventPlayer.clearStatusEffect(Status.ROOTED)

    # Player state
    eventPlayer.pauseInteraction = false

    eventPlayer.junkPos = vect(-95.58, 12.17, -86.79)
    eventPlayer.junkFacing = vect(0.21, 0.08, 0.97)

    eventPlayer.hogPos = vect(68.39, 13.91, -86.84)
    eventPlayer.hogFacing = vect(0.86, -0.51, 0)

    eventPlayer.isJunk = false
    eventPlayer.isHog = false

    eventPlayer.junkInventory = []
    eventPlayer.hogInventory = []
    eventPlayer.inventory = []

    eventPlayer.junkEquipped = []
    eventPlayer.hogEquipped = []
    eventPlayer.equipped = []

    eventPlayer.insideSmugglerItem = null
    eventPlayer.outsideSmugglerItem = null

    eventPlayer.money = 0

    # Game state
    eventPlayer.tutorialFinished = false
    eventPlayer.wiresConnected = false
    eventPlayer.generatorOn = false
    eventPlayer.powerSwitched = false
    eventPlayer.lightsOn = false

    innerMinigameInitialState()
    eventPlayer.hasFinishedInnerMinigame = false

    eventPlayer.hasFoundInnerRadio = false
    eventPlayer.canBeTheOtherGuy = false

    eventPlayer.numVentsClosed = 0
    eventPlayer.hasSunglasses = false
    eventPlayer.hasSunscreen = false
    eventPlayer.canGoOutside = false

    outerMinigameInitialState()
    eventPlayer.hasFinishedOuterMinigame = false

    # generate random 5 digit code
    eventPlayer.barnCode = [random.randint(0, 5 ** 5 - 1)]
    eventPlayer.barnCode.append(eventPlayer.barnCode[0] % 5 ** 4)
    eventPlayer.barnCode[0] = floor(eventPlayer.barnCode[0] / 5 ** 4) + 1
    eventPlayer.barnCode.append(eventPlayer.barnCode[1] % 5 ** 3)
    eventPlayer.barnCode[1] = floor(eventPlayer.barnCode[1] / 5 ** 3) + 1
    eventPlayer.barnCode.append(eventPlayer.barnCode[2] % 5 ** 2)
    eventPlayer.barnCode[2] = floor(eventPlayer.barnCode[2] / 5 ** 2) + 1
    eventPlayer.barnCode.append(eventPlayer.barnCode[3] % 5 ** 1)
    eventPlayer.barnCode[3] = floor(eventPlayer.barnCode[3] / 5 ** 1) + 1
    eventPlayer.barnCode[4]++

    eventPlayer.barnCodeInputted = []
    eventPlayer.canAccessBarnSecret = false
    eventPlayer.canLeaveBarn = false

    eventPlayer.canUtilizeSmuggler = false

    barnMinigameInitialState()
    eventPlayer.hasFinishedBarnMinigame = false

    eventPlayer.molePersonRiddleLine = 0
    eventPlayer.molePersonInteractStage = 0

    eventPlayer.bruceTimer = 0

    eventPlayer.hasFinishedGame = false

    eventPlayer.isDoingHint = false

    eventPlayer.postersFound = []
    eventPlayer.outsideMapFound = false
    eventPlayer.timesBruceClicked = 0

    # Item points

    # enum Item:
    #     ID,              # unique item id (descriptive string)
    #     STATE,           # state of the item (int)
    #     POINT,           # location of the item (vect)
    #     RADIUS,          # radius for item interaction (float)
    #     MESSAGE,         # messages for item based on state (string array)
    #     ITEM_NAME,       # name of received item if interacted with (string)
    #     PLAY_GET_SOUND,  # whether to play interaction sound (bool)
    #     NEEDS_LOS,       # whether los is needed to interact (bool)
    #     REMOVE_ON_STATE, # state at which the point is removed (int)
    #
    #     # item use points use the fields below
    #     REQUIRED_ITEM,   # item required to use with this point based on state (string array, matches with ITEM_NAME)
    #     SUCCESS_MESSAGE  # message to play on successful item use based on state (string array)

    # Interaction points
    eventPlayer.tutGate1 = [Id.tutGate1, 0, vect(-100.32, 11.44, -90.75), 1.5, ["The lasers would slice you up if you tried to go through"], null, null, null, 1, ["Rusty key"], ["How does a key open a laser gate? You aren't one to think about it too hard"]]
    eventPlayer.tutGate2 = [Id.tutGate2, 0, vect(-92.97, 11.44, -92.29), 1.5, ["The lasers would slice you up if you tried to go through"], null, null, null, 1, ["Rusty key"], null]

    eventPlayer.innerWires = [Id.innerWires, 0, vect(-106.91, 12.44, -135.82), 0.4, ["The unconnected wires are sparking", "The wires are connected"], null, null, null, null, ["Extension cord"], ["You connect the wires"]]
    eventPlayer.innerBarrel2 = [Id.innerBarrel2, 0, vect(-103.58, 7.02, -120.15), 0.5, ["There's a little bit of petrol pooled in the bottom", "The barrel is now completely empty"], null, null, null, null, ["Empty petrol can"], ["You fill up the petrol can with with the last of the petrol"]]
    eventPlayer.innerGenerator = [Id.innerGenerator, 0, vect(-109.58, 13.83, -132.75), 2.5, ["The generator is cold and lifeless", "The generator needs fuel to run", "The generator hums with a dim energy"], null, null, null, null, ["Fuel filled petrol can", "Fuel filled petrol can"], ["You pour the petrol into the generator", "You pour the petrol into the generator"]]

    eventPlayer.innerRadio = [Id.innerRadio, 0, vect(-90.05, 8.69, -103.28), 0.3, ["A radio to the outside. It seems to be missing a fiddly part though", "The radio is busted. Oops"], null, null, null, null, null, null]
    eventPlayer.innerWorkbench = [Id.innerWorkbench, 0, vect(-103.41, 8.67, -184.58), 1, null, null, null, null, null, ["Fiddly part"], ["This should help you reverse the polarity of the fiddly part"]]
    eventPlayer.innerToolbox2 = [Id.innerToolbox2, 0, vect(-103.00, 8.01, -182.63), 0.75, ["It's locked"], null, true, null, null, ["Vent key"], ["The toolbox unlocks. Inside you find some ancient sunscreen"]]

    eventPlayer.outerWorkbench = [Id.outerWorkbench, 0, vect(-56.45, 11.90, -60.51), 1, null, null, null, null, null, ["Red battery"], ["This should help you change the color of the battery"]]
    eventPlayer.outerBruce = [Id.outerBruce, 0, vect(-21.62, 7.48, -104.95), 1, null]
    eventPlayer.outerSmuggler = [Id.outerSmuggler, 0, vect(-19.06, 14.48, -88.07), 1, null]
    eventPlayer.outerBartender = [Id.outerBartender, 0, vect(-54.65, 2.73, -131.02), 1, null]
    eventPlayer.outerShadyCharacter = [Id.outerShadyCharacter, 0, vect(-35.02, 2.48, -126.33), 1, null]
    eventPlayer.outerDoor21 = [Id.outerDoor21, 0, vect(-50.47, 13.63, -84.74), 1.1, null]
    eventPlayer.outerDoor22 = [Id.outerDoor22, 0, vect(-54.41, 13.63, -84.09), 1.1, null]
    eventPlayer.outerDoor23 = [Id.outerDoor23, 0, vect(-59.72, 13.63, -85.31), 1.1, null]
    eventPlayer.outerDoor24 = [Id.outerDoor24, 0, vect(-48.46, 13.63, -90.79), 1.1, null]
    eventPlayer.outerDoor25 = [Id.outerDoor25, 0, vect(-42.10, 14.63, -94.75), 1.1, null]

    eventPlayer.barnWorkbench = [Id.barnWorkbench, 0, vect(48.86, 10.21, -81.42), 1, null, null, null, null, null, ["Right handed remote"], ["This should help you make the remote left handed"]]

    eventPlayer.outsideHose = [Id.outsideHose, 0, vect(-31.21, 6.36, -27.83), 0.7, ["The hose leads down from the water tower", "You don't need any more water"], null, null, null, null, ["Empty petrol can"], ["You fill up the can with water"]]
    eventPlayer.outsideSatControl = [Id.outsideSatControl, 0, vect(-3.30, 14.20, -83.94), 1, ["This controls the satellite connection to your friend Bruce, but you left the remote control at his place", "You've already talked to Bruce"], null, null, null, null, ["Left handed remote"], ["You use the remote on the controller"]]
    eventPlayer.outsideSmuggler = [Id.outsideSmuggler, 0, vect(-44.52, 5.49, -49.63), 1, null]
    eventPlayer.outsideMolePersonRoom = [Id.outsideMolePersonRoom, 0, vect(-50.68, 5.97, -4.12), 2, null]
    eventPlayer.outsideMolePersonOuthouse = [Id.outsideMolePersonOuthouse, 0, MOLE_PERSON_OUTHOUSE_TEXT_LOCATION, 2, null]
    eventPlayer.outsideMolePersonCar = [Id.outsideMolePersonCar, 0, MOLE_PERSON_CAR_TEXT_LOCATION, 2, null]
    eventPlayer.outsideMolePersonCliff = [Id.outsideMolePersonCliff, 0, MOLE_PERSON_CLIFF_TEXT_LOCATION, 2, null]

    eventPlayer.itemPoints = [
        # tutorial room
        [Id.tutSafe1, 0, vect(-104.92, 12.06, -84.28), 1.2, ["All that's inside are a few dusty cobwebs"], null, true],
        [Id.tutSafe2, 0, vect(-100.95, 11.28, -80.59), 0.7, ["There's an odd looking coin in here"], "Odd looking coin", true],
        [Id.tutSafe3, 0, vect(-103.16, 11.95, -76.61), 1, ["The box is buried under a mound of coins and can't be opened"], null, true],
        [Id.tutSafe4, 0, vect(-89.28, 12.44, -72.22), 1.2, ["The door of the safe is too heavy to lift"], null, true],
        [Id.tutSafe5, 0, vect(-88.78, 11.51, -90.72), 0.7, ["The door squeaks open and you find a rusty key inside"], null, true],
        [Id.tutGold, 0, vect(-95.60, 11.38, -86.96), 1.5, ["Is this... how you were brought in here?"]],

        [Id.tutBarrel1, 0, vect(-100.70, 11.04, -77.17), 0.5, ["All of the barrels in here seem to be empty"]],
        [Id.tutBarrel2, 0, vect(-101.63, 11.30, -76.47), 0.5, ["All of the barrels in here seem to be empty"]],
        [Id.tutBarrel3, 0, vect(-101.72, 11.70, -75.09), 0.5, ["All of the barrels in here seem to be empty"]],
        [Id.tutBarrel4, 0, vect(-88.59, 11.70, -73.35), 0.5, ["All of the barrels in here seem to be empty"]],
        [Id.tutBarrel5, 0, vect(-83.85, 11.78, -80.16), 0.5, ["All of the barrels in here seem to be empty"]],
        [Id.tutBarrel6, 0, vect(-87.39, 11.07, -84.29), 0.5, ["All of the barrels in here seem to be empty"]],
        [Id.tutBarrel7, 0, vect(-88.16, 10.97, -85.22), 0.5, ["All of the barrels in here seem to be empty"]],

        # lights on section
        [Id.innerFridge, 0, vect(-110.04, 6.97, -111.51), 1.1, ["All that's inside are fish heads and moldy leftovers"], null, true],
        [Id.innerChest1, 0, vect(-114.91, 7.07, -124.08), 1.2, ["There's an empty petrol can inside, just free for the taking"], "Empty petrol can", true],
        [Id.innerLocker1, 0, vect(-112.76, 7.55, -134.80), 1.2, ["Whoever's locker this was didn't have the decency to leave easily stolen goods inside"], null, true],
        [Id.innerLocker2, 0, vect(-113.80, 7.55, -139.10), 1.2, ["There's a note: \"Keep your hands off my stuff you scrawny eyed mole rats!\""], null, true],
        [Id.innerLocker3, 0, vect(-112.58, 7.55, -141.24), 1.2, ["The locker is completely devoid of utility"], null, true],
        [Id.innerLocker4, 0, vect(-108.08, 7.55, -142.79), 1.2, ["There's an old extension cord hidden in the back"], "Extension cord", true],
        [Id.innerLocker5, 0, vect(-106.71, 7.55, -141.16), 1.2, ["You don't even want to think about whose swimsuit that is"], null, true],
        [Id.innerPowerDistributor, 0, vect(-110.71, 13.19, -136.03), 0.8, null],

        [Id.innerBarrel1, 0, vect(-113.43, 7.02, -116.14), 0.5, ["The barrel is as dry as a day in December"], null, true],
        [Id.innerBarrel3, 0, vect(-105.32, 12.79, -132.71), 0.5, ["It's as empty as the depths of your soul"], null, true],
        [Id.innerNotBarrel, 0, vect(-104.5, 7.64, -121.27), 1, ["This doesn't hold petrol, but maybe if you look nearby"]],

        [Id.innerSwitch1, 0, vect(-111.46, 14.145, -137.47), 0.05, null],
        [Id.innerSwitch2, 0, vect(-111.46, 14.071, -137.47), 0.05, null],
        [Id.innerSwitch3, 0, vect(-111.46, 13.994, -137.47), 0.05, null],
        [Id.innerSwitch4, 0, vect(-111.46, 13.914, -137.47), 0.05, null],
        [Id.innerSwitch5, 0, vect(-111.46, 13.835, -137.47), 0.05, null],
        [Id.innerPowerBox, 0, vect(-111.73, 14.04, -137.34), 0.5, ["This is the power switching box for the whole area", "Maybe if you get the generator running you can turn the lights on", "There's no power going to the box"]],

        # larger inner section
        eventPlayer.innerRadio, # higher up for priority reasons

        [Id.innerThrone, 0, vect(-88.94, 8.40, -102.99), 0.75, ["The queen's throne... oh how you want to sit on it"]],

        [Id.innerChest2, 0, vect(-101.16, 8.12, -198.06), 1.2, ["You pick up a battery covered in peeling red paint"], "Red battery", true],
        [Id.innerToolbox1, 0, vect(-103.25, 9.22, -182.63), 0.75, ["There's a note: \"Threw key in vent by inner gate. Long as the three valves are open, no one can grab it.\""], null, true],
        [Id.innerSmallToolbox1, 0, vect(-102.65, 9.06, -181.25), 0.5, ["Someone just left a fiddly part in the toolbox? It must be your lucky day"], "Fiddly part", true],
        [Id.innerTireRack, 0, vect(-87.96, 8.63, -201.60), 1.8, ["Why are there rubber tires when all the cars hover?"]],

        [Id.innerBrokenMech1, 0, vect(-88.35, 8.26, -142.03), 2, ["The mech seems to be missing a limb. You sympathize with it"]],
        [Id.innerBrokenMech2, 0, vect(-75.42, 8.52, -146.11), 2, ["This mech doesn't even have a saw blade on it"]],
        [Id.innerMech1, 0, vect(-90.47, 10.09, -177.75), 2, ["Now *that's* a sawblade"]],
        [Id.innerMech2, 0, vect(-94.87, 10.11, -178.01), 2, ["The old king's mech. You've never seen it up close before"]],

        [Id.innerMinigameControl1, 0, vect(-103.45, 8.89, -184.36), 0.1, null],
        [Id.innerMinigameControl2, 0, vect(-103.50, 8.89, -184.63), 0.1, null],
        [Id.innerMinigameSkip, 0, INNER_MINIGAME_2D_TO_3D(vect(1.48, 0.73, 0)), 0.05, null],

        [Id.innerSunglasses, 0, vect(-88.04, 13.37, -158.22), 1, ["You find some tattered sunglasses hidden in the corner", "You've already scoured this area"]],

        [Id.innerValve1, 0, vect(-85.83, 8.43, -178.64), 0.5, [null, "The valve is already closed"]],
        [Id.innerValve2, 0, vect(-54.84, 9.57, -143.66), 0.5, [null, "The valve is already closed"]],
        [Id.innerValve3, 0, vect(-53.58, 9.91, -122.34), 0.5, [null, "The valve is already closed"]],

        [Id.innerVent1, 0, vect(-108.61, 10.46, -108.17), 1, ["You don't see anything in here"]],
        [Id.innerVent2, 0, vect(-84.87, 11.34, -186.75), 1, ["You don't see anything in here"]],
        [Id.innerVent3, 0, vect(-54.90, 10.74, -125.39), 1, null],
        [Id.innerVent4, 0, vect(-75.87, 14.17, -123.53), 2, ["You don't see anything in here"]],
        [Id.innerVent5, 0, vect(-69.87, 15.81, -107.26), 1, ["You don't see anything in here"]],
        [Id.innerVent6, 0, vect(-94.74, 13.59, -177.53), 1, ["You don't see anything in here"]],
        [Id.innerVent7, 0, vect(-86.38, 14.14, -180.01), 1.8, ["You don't see anything in here"]],


        # courtyard area
        [Id.outerIceBox, 0, vect(-50.77, 2.49, -126.77), 1, ["The harsh look from the bartender keeps you from poking around inside"], null, true],
        [Id.outerSmallToolbox1, 0, vect(-11.03, 8.29, -103.06), 0.5, ["The toolbox is far too heavy for its size"], null, true],
        [Id.outerToolbox1, 0, vect(-61.25, 11.55, -75.41), 0.75, ["This looks like a... frog mask? You may as well put it on"], null, true],
        [Id.outerToolbox2, 0, vect(-9.54, 8.40, -104.89), 0.75, ["You don't know what half these tools do. Okay, all of them"], null, true],
        [Id.outerToolbox3, 0, vect(-10.33, 7.33, -102.96), 0.75, ["You find an old remote inside"], "Right handed remote", true],
        [Id.outerRadio, 0, vect(-10.46, 8.35, -105.80), 0.3, ["This allows incoming connections from outside as long as they're broadcasting on the right frequency"]],
        [Id.outerMic, 0, vect(-30.95, 3.80, -135.05), 0.5, null],
        [Id.outerWaterBarrel, 0, vect(-17.72, 7.95, -90.27), 3, ["You've never been able to figure out how to get water out of here"]],

        [Id.outerCart, 0, vect(-41.66, 6.20, -66.89), 3, ["The cart is stopped in the middle of the road"]],

        [Id.outerPoster1, 0, vect(-64.74, 8.68, -102.96), 0.8, ["A grossly unfair characterization"], null, null, true],
        [Id.outerPoster2, 0, vect(-60.39, 14.28, -88.06), 0.5, ["You don't want to be here anyway"], null, null, true],
        [Id.outerPoster3, 0, vect(-48.70, 8.39, -95.52), 0.8, ["It was all just a big misunderstanding"], null, null, true],
        [Id.outerPoster4, 0, vect(-45.22, 8.42, -112.92), 0.5, ["The Queen was too attached to that summer shack anyway"], null, null, true],
        [Id.outerPoster5, 0, vect(-42.93, 8.67, -116.01), 0.5, ["Really, you were doing her a favor"], null, null, true],
        [Id.outerPoster6, 0, vect(-41.97, 14.58, -120.91), 0.5, ["The picture has your nose all wrong"], null, null, true],
        [Id.outerPoster7, 0, vect(-26.17, 14.43, -112.75), 0.5, ["Come on, what's a few explosions between friends"], null, null, true],
        [Id.outerPoster8, 0, vect(-20.13, 8.15, -95.48), 0.7, ["And you thought this used to be a respectable establishment"], null, null, true],
        [Id.outerPoster9, 0, vect(-32.28, 7.78, -94.74), 0.5, ["This is completely unnecessary"], null, null, true],
        [Id.outerPoster10, 0, vect(-31.35, 7.44, -85.96), 1.1, ["Don't people deserve a sixth chance?"], null, null, true],
        [Id.outerPosterOob11, 0, vect(-44.65, 6.38, -76.55), 0.7, ["\"Troublemakers\"? The nerve"], null, null, true],
        [Id.outerPosterOob12, 0, vect(-35.80, 5.70, -70.76), 1, ["You feel wounded at such accusations"], null, null, true],
        [Id.outerPosterOob13, 0, vect(-44.66, 6.73, -60.23), 1.1, ["Maybe if you just had a little chat with the Queen"], null, null, true],
        [Id.outerPosterOob14, 0, vect(-33.01, 5.96, -63.99), 0.7, ["There's no need to go to such extremes"], null, null, true],
        [Id.outerPosterOob15, 0, vect(-54.26, 5.98, -47.88), 0.7, ["What's the point of putting a poster all the way over here?"], null, null, true],

        [Id.outerBinoculars, 0, vect(-36.64, 14.02, -59.19), 0.3],

        [Id.outerMinigameDot1, 0, OUTER_MINIGAME_2D_TO_3D(vect(0.72, 0.08 + 0.17 * 3, 0)), OUTER_MINIGAME_BALL_SIZE, null],
        [Id.outerMinigameDot2, 0, OUTER_MINIGAME_2D_TO_3D(vect(0.62, 0.08 + 0.17 * 2, 0)), OUTER_MINIGAME_BALL_SIZE, null],
        [Id.outerMinigameDot3, 0, OUTER_MINIGAME_2D_TO_3D(vect(0.82, 0.08 + 0.17 * 2, 0)), OUTER_MINIGAME_BALL_SIZE, null],
        [Id.outerMinigameDot4, 0, OUTER_MINIGAME_2D_TO_3D(vect(0.52, 0.08 + 0.17, 0)), OUTER_MINIGAME_BALL_SIZE, null],
        [Id.outerMinigameDot5, 0, OUTER_MINIGAME_2D_TO_3D(vect(0.72, 0.08 + 0.17, 0)), OUTER_MINIGAME_BALL_SIZE, null],
        [Id.outerMinigameDot6, 0, OUTER_MINIGAME_2D_TO_3D(vect(0.92, 0.08 + 0.17, 0)), OUTER_MINIGAME_BALL_SIZE, null],
        [Id.outerMinigameDot7, 0, OUTER_MINIGAME_2D_TO_3D(vect(0.42, 0.08, 0)), OUTER_MINIGAME_BALL_SIZE, null],
        [Id.outerMinigameDot8, 0, OUTER_MINIGAME_2D_TO_3D(vect(0.62, 0.08, 0)), OUTER_MINIGAME_BALL_SIZE, null],
        [Id.outerMinigameDot9, 0, OUTER_MINIGAME_2D_TO_3D(vect(0.82, 0.08, 0)), OUTER_MINIGAME_BALL_SIZE, null],
        [Id.outerMinigameDot10, 0, OUTER_MINIGAME_2D_TO_3D(vect(1.02, 0.08, 0)), OUTER_MINIGAME_BALL_SIZE, null],
        [Id.outerMinigameRotate1, 0, OUTER_MINIGAME_2D_TO_3D(vect(0.72, 0.08 + 0.17 * 7 / 3, 0)), 0.08, null],
        [Id.outerMinigameRotate2, 0, OUTER_MINIGAME_2D_TO_3D(vect(0.52, 0.08 + 0.17 / 3, 0)), 0.08, null],
        [Id.outerMinigameRotate3, 0, OUTER_MINIGAME_2D_TO_3D(vect(0.92, 0.08 + 0.17 / 3, 0)), 0.08, null],
        [Id.outerMinigameSkip, 0, OUTER_MINIGAME_2D_TO_3D(vect(1.15, 0.59, 0)), 0.05, null],


        # barn area
        [Id.barnRadio, 0, vect(69.46, 15.15, -86.83), 0.7, ["That idiot managed to blow up the only working radio"]],
        [Id.barnTvPanel, 0, vect(70.40, 15.44, -82.28), 0.4, ["You don't need to change the TV channel right now"]],
        [Id.barnPachimari, 0, vect(69.81, 10.58, -80.19), 1.2, ["Despite your love for them, you were never any good at these things"]],
        [Id.barnFuelMachine, 0, vect(69.01, 10.34, -84.15), 1.2, ["This machine helps you survive the irradiated outdoors"], null, true],
        [Id.barnFridge, 0, vect(66.91, 10.37, -67.64), 1, ["The fridge is full of half eaten Chinese food"], null, true],
        [Id.barnTireRack, 0, vect(44.07, 10.12, -88.71), 1.5, ["This rack of hover pads is entirely unremarkable"], null, null, true],
        [Id.barnPlan, 0, vect(44.03, 11.11, -71.35), 1.5, ["Things didn't to according to plan"], null, null, true],

        [Id.barnToolbox1, 0, vect(49.62, 10.55, -75.25), 0.75, ["The toolbox is filled with a bunch of spare spark plugs"], null, true],
        [Id.barnToolbox2, 0, vect(50.55, 9.42, -75.90), 0.75, ["There's a meticulously arranged collection of screwdrivers, but nothing you need right now"], null, true],
        [Id.barnSmallToolbox1, 0, vect(49.20, 10.27, -79.17), 0.5, ["This was always your least favorite toolbox"], null, true],
        [Id.barnSafe1, 0, vect(60.60, 10.01, -65.34), 1, ["That's private, there's no need to look in there"], null, true, true],
        [Id.barnSafe2, 0, vect(60.52, 9.50, -63.60), 0.8, ["You pick up your little buddy and place him carefully in your bag"], null, true],
        [Id.barnSafe3, 0, vect(56.13, 9.74, -62.70), 0.7, ["This safe is empty, you just put it there for the aesthetic"], null, true, true],
        [Id.barnEmptyShelf, 0, vect(50.05, 10.56, -63.46), 2, ["Times have been rough lately"], null, true, true],

        [Id.barnPaperClue1, 0, vect(58.30, 9, -93.83), 0.3, null],
        [Id.barnPaperClue2, 0, vect(42.25, 16, -71.88), 0.3, null],
        [Id.barnBike, 0, vect(54.56, 10.65, -78.11), 2.5, null],

        [Id.barnSwitch1, 0, vect(56.48, 10.655, -66.584), 0.05, null],
        [Id.barnSwitch2, 0, vect(56.48, 10.581, -66.584), 0.05, null],
        [Id.barnSwitch3, 0, vect(56.48, 10.504, -66.584), 0.05, null],
        [Id.barnSwitch4, 0, vect(56.48, 10.424, -66.584), 0.05, null],
        [Id.barnSwitch5, 0, vect(56.48, 10.345, -66.584), 0.05, null],
        [Id.barnCodePanel, 0, vect(56.55, 10.50, -66.74), 0.5, ["This opens your secret stash, if only you could remember the code"]],

        [Id.barnMinigameDot0, 0, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[0]), 0.05, null],
        [Id.barnMinigameDot1, 0, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[1]), 0.05, null],
        [Id.barnMinigameDot2, 0, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[2]), 0.05, null],
        [Id.barnMinigameDot3, 0, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[3]), 0.05, null],
        [Id.barnMinigameDot4, 0, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[4]), 0.05, null],
        [Id.barnMinigameDot5, 0, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[5]), 0.05, null],
        [Id.barnMinigameDot6, 0, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[6]), 0.05, null],
        [Id.barnMinigameDot7, 0, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[7]), 0.05, null],
        [Id.barnMinigameDot8, 0, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[8]), 0.05, null],
        [Id.barnMinigameSkip, 0, BARN_MINIGAME_2D_TO_3D(vect(1.4, 0.8, 0)), 0.05, null],


        # larger outside area
        eventPlayer.outsideHose, # here for priority reasons

        [Id.outsideJunkWorkbench, 0, vect(31.57, 10.71, -95.93), 1.2, ["That idiot was always working on some dumb scheme"]],
        [Id.outsideSafe, 0, vect(21.24, 10.90, -88.37), 0.7, ["You don't what to know what kind of stuff he keeps in there"], null, true],
        [Id.outsideIceBox, 0, vect(-32.99, 5.36, -29.36), 1, ["It smells like something's rotted in there"], null, true, true],
        [Id.outsideFridge1, 0, vect(20.48, 11.05, -95.08), 1, ["There's a map with a the word \"sunglasses\" pointing to a red dot"]],
        [Id.outsideFridge2, 0, vect(-5.68, 13.51, -74.65), 1.2, ["Without electricity, this fridge isn't doing a very good job"], null, true],
        [Id.outsideFridge3, 0, vect(-31.79, 5.62, -28.84), 0.8, ["You can't open the door"], null, true, true],
        [Id.outsideFridge4, 0, vect(-31.72, 5.57, -30.10), 0.8, ["The door won't budge"], null, true, true],
        [Id.outsideToolbox1, 0, vect(-6.67, 8.55, -77.17), 0.7, ["There's only some ancient pistons in here"], null, true],
        [Id.outsideToolbox2, 0, vect(-6.55, 7.60, -77.18), 0.7, ["You don't need any of the car parts inside since you have a motorcycle"], null, true],
        [Id.outsideSmallToolbox, 0, vect(24.28, 9.26, -64.30), 0.5, ["It seems to hold the moldy remains of someone's lunch"], null, true],
        [Id.outsideChest1, 0, vect(19.26, 8.73, -55.36), 1.2, ["Anything in here has been looted a long time ago"], null, true],
        [Id.outsideChest2, 0, vect(-7.98, 8.35, -47.93), 1.2, ["It might be useful for standing on, but not much else"], null, true],
        [Id.outsideChest3, 0, vect(-7.98, 6.58, -47.93), 1.2, ["This chest is trapped under the other one"], null, true],
        [Id.outsideChest4, 0, vect(-35.12, 5.22, -43.65), 1.2, ["The only things inside are some half dead brambles"], null, true],
        [Id.outsideChest5, 0, vect(-50.83, 4.92, -16.62), 1.2, ["Nothing up your sleeve, or in the chest"], null, true],

        [Id.outsidePoster1, 0, vect(-30.90, 6.14, -60.22), 0.8, ["That idiot is always getting you in trouble"], null, null, true],
        [Id.outsidePoster2, 0, vect(-23.46, 6.70, -66.49), 0.8, ["There's not much you need in there, but you'll miss talking to Bruce"], null, null, true],

        # item apply points
        eventPlayer.tutGate1,
        eventPlayer.tutGate2,

        eventPlayer.innerWires,
        eventPlayer.innerBarrel2,
        eventPlayer.innerGenerator,
        # eventPlayer.innerRadio, # higher up for priority reasons
        eventPlayer.innerWorkbench,
        eventPlayer.innerToolbox2,

        eventPlayer.outerBruce,
        eventPlayer.outerSmuggler,
        eventPlayer.outerBartender,
        eventPlayer.outerShadyCharacter,
        eventPlayer.outerWorkbench,
        eventPlayer.outerDoor21,
        eventPlayer.outerDoor22,
        eventPlayer.outerDoor23,
        eventPlayer.outerDoor24,
        eventPlayer.outerDoor25,

        eventPlayer.barnWorkbench,

        eventPlayer.outsideSatControl,
        eventPlayer.outsideSmuggler,
        # eventPlayer.outsideHose, # higher up for priority reasons
        eventPlayer.outsideMolePersonRoom,
        eventPlayer.outsideMolePersonOuthouse,
        eventPlayer.outsideMolePersonCar,
        eventPlayer.outsideMolePersonCliff,
    ]

    eventPlayer.itemIds = [elem[Item.ID] for elem in eventPlayer.itemPoints]

    # Other state setup
    introStage1Players.remove(eventPlayer)
    introStage2Players.remove(eventPlayer)

    # tutorial gate setup
    eventPlayer.tutorialGate1End = vect(-101.64, 10.93, -90.46)
    eventPlayer.tutorialGate2End = vect(-91.58, 10.93, -92.60)

    eventPlayer.inventoryPlayer = []

    manageEquips()
    manageAchievements()

    tutorialTextPlayers[TutorialText.INTERACT].remove(eventPlayer)
    tutorialTextPlayers[TutorialText.INTERACT].append(eventPlayer)
    tutorialTextPlayers[TutorialText.INV_CYCLE].remove(eventPlayer)
    tutorialTextPlayers[TutorialText.APPLY_ITEM].remove(eventPlayer)
    tutorialTextPlayers[TutorialText.HINTS].remove(eventPlayer)
    tutorialPlayers.remove(eventPlayer)
    tutorialPlayers.append(eventPlayer)
    graphicsQualityPlayers.remove(eventPlayer)

    playersInDark.remove(eventPlayer)

    innerWirePlayers.remove(eventPlayer)

    powerPanelPlayers[0].remove(eventPlayer)
    powerPanelPlayers[0].append(eventPlayer)
    powerPanelPlayers[1].remove(eventPlayer)
    powerPanelPlayers[1].append(eventPlayer)
    powerPanelPlayers[2].remove(eventPlayer)
    powerPanelPlayers[2].append(eventPlayer)
    powerPanelPlayers[3].remove(eventPlayer)
    powerPanelPlayers[3].append(eventPlayer)
    powerPanelPlayers[4].remove(eventPlayer)
    powerPanelPlayers[4].append(eventPlayer)

    innerMinigamePlayers.remove(eventPlayer)
    radioSmokePlayers.remove(eventPlayer)

    outerMinigamePlayers.remove(eventPlayer)

    mapMarkerPlayers.remove(eventPlayer)

    barnHintPlayers.remove(eventPlayer)
    barnHintPlayers.append(eventPlayer)

    barnMinigamePlayers.remove(eventPlayer)

    dvdLogoPlayers.remove(eventPlayer)
    dvdLogoPlayers.append(eventPlayer)
    satControlPlayers.remove(eventPlayer)

    insideMailTextPlayers.remove(eventPlayer)
    outsideMailTextPlayers.remove(eventPlayer)

    eventPlayer.startForcingHero(Hero.JUNKRAT)
    eventPlayer.teleport(eventPlayer.junkPos)
    eventPlayer.setFacing(eventPlayer.junkFacing, Relativity.TO_WORLD)
    wait(RACE_CONDITION_DELAY)
    eventPlayer.isJunk = true

def resetGame():
    FADE_TO_BLACK()

    popAllEffectDeleteQueue()
    popAllTextDeleteQueue()
    playerInitialState()

    eventPlayer.timeStarted = timer
    eventPlayer.currentCutscene = Cutscene.WAKING

rule "Show intro cutscenes":
    @Event playerJoined
    @Team 1

    eventPlayer.isCutsceneFinished = false
    eventPlayer.currentCutscene = Cutscene.INTRO

rule "Initial player setup":
    @Event playerJoined
    @Team 1

    playerInitialState()
    eventPlayer.achievements = [] # achievements aren't reset on game finish

    eventPlayer.setStatusEffect(null, Status.INVINCIBLE, INF)
    eventPlayer.setInvisibility(Invis.ENEMIES)
    eventPlayer.disableGamemodeHud()
    eventPlayer.disableScoreboard()
    eventPlayer.disableKillFeed()
    eventPlayer.disableGamemodeInWorldUi()
    eventPlayer.disableMessages()
    eventPlayer.disablePlayerCollision()

    hudHeader(eventPlayer.inventoryPlayer, "Inventory: {0}".format(eventPlayer.inventory), HudPosition.LEFT, 10, Color.WHITE, HudReeval.VISIBILITY_AND_STRING, SpecVisibility.DEFAULT)

rule "Cleanup state on player leave":
    @Event playerLeft

    skipCutscenePlayers.remove(eventPlayer)
    introStage1Players.remove(eventPlayer)
    introStage2Players.remove(eventPlayer)
    tutorialPlayers.remove(eventPlayer)
    graphicsQualityPlayers.remove(eventPlayer)
    playersInDark.remove(eventPlayer)
    innerWirePlayers.remove(eventPlayer)
    innerMinigamePlayers.remove(eventPlayer)
    radioSmokePlayers.remove(eventPlayer)
    outerMinigamePlayers.remove(eventPlayer)
    barnHintPlayers.remove(eventPlayer)
    mapMarkerPlayers.remove(eventPlayer)
    barnMinigamePlayers.remove(eventPlayer)
    dvdLogoPlayers.remove(eventPlayer)
    satControlPlayers.remove(eventPlayer)
    insideMailTextPlayers.remove(eventPlayer)
    outsideMailTextPlayers.remove(eventPlayer)

    for eventPlayer.I in range(len(powerPanelPlayers)):
        powerPanelPlayers[eventPlayer.I].remove(eventPlayer)

    for eventPlayer.I in range(len(tutorialTextPlayers)):
        tutorialTextPlayers[eventPlayer.I].remove(eventPlayer)

    for eventPlayer.I in range(len(equipPlayers)):
        equipPlayers[eventPlayer.I].remove(eventPlayer)

    for eventPlayer.I in range(len(achievementPlayers)):
        achievementPlayers[eventPlayer.I].remove(eventPlayer)



# CUTSCENES

rule "skip cutscene text":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene != Cutscene.NONE

    skipCutscenePlayers.append(eventPlayer)
    stopChasingVariable(eventPlayer.skipAlpha)
    eventPlayer.skipAlpha = 255
    wait(5)
    chase(eventPlayer.skipAlpha, 0, duration=0.6, ChaseReeval.NONE)
    wait(0.6)
    skipCutscenePlayers.remove(eventPlayer)

rule "skip cutscene":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene != Cutscene.NONE
    @Condition eventPlayer.isHoldingButton(Button.JUMP)

    wait(1, Wait.ABORT_WHEN_FALSE)
    eventPlayer.isCutsceneFinished = true
    skipCutscenePlayers.remove(eventPlayer)

rule "show intro":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.INTRO and not eventPlayer.isCutsceneFinished

    wait(1, Wait.ABORT_WHEN_FALSE)
    hideUi()
    eventPlayer.setInvisibility(Invis.ALL)
    introStage1Players.append(eventPlayer)

    FADE_TO_BLACK()

    wait(3.5, Wait.ABORT_WHEN_FALSE)
    wait(1, Wait.ABORT_WHEN_FALSE)

    # Stage 1, view of plan
    eventPlayer.tPos = vect(45.98, 11.12, -71.48) + vect(-1, -0.01, 0.10) * 0.3
    eventPlayer.startCamera(eventPlayer.tPos, vect(45.98, 11.12, -71.48) + vect(-1, -0.01, 0.10), 0)
    chase(eventPlayer.tPos, vect(45.98, 11.12, -71.48) - vect(-1, -0.01, 0.10) * 0.2, duration=3.3, ChaseReeval.DESTINATION_AND_DURATION)

    wait(3.3, Wait.ABORT_WHEN_FALSE)

    stopChasingVariable(eventPlayer.tPos)

    # Stage 2, following cart path
    smallMessage(eventPlayer, "But something went wrong")

    eventPlayer.bezier = [
        vect(7.60, 8.28, -68.38),
        vect(8.22, 8.32, -64.54),
        vect(8.84, 8.36, -60.69),
        vect(6.69, 8.28, -56.59),
        vect(4.54, 8.20, -52.48),
        vect(-0.38, 8.00, -48.11),
        vect(-5.31, 7.79, -43.75),
    ]
    eventPlayer.bezierDuration = 1.65
    eventPlayer.startCamera(BEZIER_POSITION, BEZIER_FACING, 0)
    doBezierArray()

    # Stage 3, view of posters
    eventPlayer.tPos = vect(-36.85, 5.72, -72.38) + vect(0.55, 0.12, 0.83) * 0.3
    eventPlayer.startCamera(eventPlayer.tPos, vect(-36.85, 5.72, -72.38) + vect(0.55, 0.12, 0.83), 0)
    chase(eventPlayer.tPos, vect(-36.85, 5.72, -72.38) - vect(0.55, 0.12, 0.83) * 0.2, duration=3.3, ChaseReeval.DESTINATION_AND_DURATION)

    wait(3.3, Wait.ABORT_WHEN_FALSE)

    stopChasingVariable(eventPlayer.tPos)

    # Stage 4
    introStage1Players.remove(eventPlayer)

    eventPlayer.tPos = vect(26.97, 21.97, 47.25)
    eventPlayer.startCamera(eventPlayer.tPos, vect(-35.24, 11.71, -55.88), 0)
    chase(eventPlayer.tPos, vect(-33.71, 11.70, -53.29), duration=2.80, ChaseReeval.DESTINATION_AND_DURATION)

    wait(4.6, Wait.ABORT_WHEN_FALSE)

    stopChasingVariable(eventPlayer.tPos)

    # Title view
    introStage2Players.append(eventPlayer)
    playEffect(eventPlayer, DynamicEffect.JUNKRAT_RIP_TIRE_EXPLOSION, null, vect(-35.24, 12.71, -55.88), 10)
    playEffect(eventPlayer, DynamicEffect.JUNKRAT_RIP_TIRE_EXPLOSION_SOUND, null, vect(-35.24, 12.71, -55.88), 80)

    wait(4, Wait.ABORT_WHEN_FALSE)

    FADE_TO_BLACK()

    introStage2Players.remove(eventPlayer)

    eventPlayer.isCutsceneFinished = true

rule "show intro cleanup":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.INTRO and eventPlayer.isCutsceneFinished

    eventPlayer.currentCutscene = Cutscene.NONE
    eventPlayer.isCutsceneFinished = false

    stopChasingVariable(eventPlayer.tPos)
    introStage1Players.remove(eventPlayer)
    introStage2Players.remove(eventPlayer)
    eventPlayer.setInvisibility(Invis.NONE)

    resetGame()

#!define INTRO_SPACING "\n                                                                                                                     "

rule "do waking animation":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.WAKING and not eventPlayer.isCutsceneFinished

    freeze()
    hideUi()
    FADE_TO_BLACK()
    wait(2, Wait.ABORT_WHEN_FALSE)

    CREATE_BLACK_TEXT("What... happened?" INTRO_SPACING, -2)
    wait(3, Wait.ABORT_WHEN_FALSE)
    CREATE_BLACK_TEXT("The last thing you remember is pushing the cart to the gate" INTRO_SPACING, -1)
    wait(3, Wait.ABORT_WHEN_FALSE)
    CREATE_BLACK_TEXT("Then a pain from behind" INTRO_SPACING, 0)
    wait(3, Wait.ABORT_WHEN_FALSE)
    CREATE_BLACK_TEXT("And..." INTRO_SPACING, 1)
    wait(2, Wait.ABORT_WHEN_FALSE)
    destroyInWorldText(eventPlayer.textDeleteQueue[len(eventPlayer.textDeleteQueue) - 1])
    del eventPlayer.textDeleteQueue[len(eventPlayer.textDeleteQueue) - 1]
    CREATE_BLACK_TEXT("And... a fuzzy face?" INTRO_SPACING, 1)
    wait(6, Wait.ABORT_WHEN_FALSE)

    eventPlayer.isCutsceneFinished = true

rule "do waking animation cleanup":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.WAKING and eventPlayer.isCutsceneFinished

    eventPlayer.currentCutscene = Cutscene.NONE
    eventPlayer.isCutsceneFinished = false

    eventPlayer.inventoryPlayer.append(eventPlayer)
    unfreeze()
    showUi()
    eventPlayer.stopCamera()
    popAllTextDeleteQueue()
    wait(3)
    graphicsQualityPlayers.append(eventPlayer)

#!define RADIO_TEXT_POS vect(-90.05, 8.69 + 0.3, -103.28)

rule "do inner radio conversation":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.INNER_RADIO and not eventPlayer.isCutsceneFinished

    freeze()
    hideUi()
    eventPlayer.startFacing(directionTowards(eventPlayer.getEyePosition(), RADIO_TEXT_POS - vect(0, 0.2, 0)), 60, Relativity.TO_WORLD, FacingReeval.DIRECTION_AND_TURN_RATE)

    smallMessage(eventPlayer, "You attempt to contact the other guy")

    wait(3, Wait.ABORT_WHEN_FALSE)

    CREATE_TEXT("*krssshksht*", RADIO_TEXT_POS)
    createEffect(eventPlayer, Effect.MERCY_DAMAGE_BOOSTED_SOUND, null, eventPlayer.innerRadio[Item.POINT], 100, EffectReeval.NONE)
    pushEffectDeleteQueue()

    wait(2, Wait.ABORT_WHEN_FALSE)
    popEffectDeleteQueue()
    popTextDeleteQueue()

    wait(2, Wait.ABORT_WHEN_FALSE)
    CREATE_TEXT("...what did you get yourself into this time.", RADIO_TEXT_POS)

    wait(5, Wait.ABORT_WHEN_FALSE)
    CREATE_TEXT("You woke up in a safe full of gold?", RADIO_TEXT_POS)

    wait(5, Wait.ABORT_WHEN_FALSE)
    CREATE_TEXT("In the queen's treasure room?!", RADIO_TEXT_POS)

    wait(5, Wait.ABORT_WHEN_FALSE)
    popTextDeleteQueue()
    wait(1, Wait.ABORT_WHEN_FALSE)
    CREATE_TEXT("        \n.", RADIO_TEXT_POS)
    wait(1, Wait.ABORT_WHEN_FALSE)
    CREATE_TEXT("        \n. .", RADIO_TEXT_POS)
    wait(1, Wait.ABORT_WHEN_FALSE)
    CREATE_TEXT("        \n. . .", RADIO_TEXT_POS)
    wait(2, Wait.ABORT_WHEN_FALSE)
    popTextDeleteQueue()

    wait(2, Wait.ABORT_WHEN_FALSE)
    CREATE_TEXT("Well, just try to get outside right now.", RADIO_TEXT_POS)

    wait(5, Wait.ABORT_WHEN_FALSE)
    CREATE_TEXT("Find Bruce, he might be able to help you.", RADIO_TEXT_POS)

    wait(5, Wait.ABORT_WHEN_FALSE)
    CREATE_TEXT("I'll let him know to expect you, he doesn't trust strangers.", RADIO_TEXT_POS)

    wait(5, Wait.ABORT_WHEN_FALSE)
    popTextDeleteQueue()

    wait(3, Wait.ABORT_WHEN_FALSE)
    CREATE_TEXT("What? No an explosion won't hel-", RADIO_TEXT_POS)

    wait(1)
    popTextDeleteQueue()
    playEffect(eventPlayer, DynamicEffect.JUNKRAT_RIP_TIRE_EXPLOSION, null, eventPlayer.innerRadio[Item.POINT], 100)
    playEffect(eventPlayer, DynamicEffect.JUNKRAT_RIP_TIRE_EXPLOSION_SOUND, null, eventPlayer.innerRadio[Item.POINT], 100)
    radioSmokePlayers.append(eventPlayer)
    wait(2)
    smallMessage(eventPlayer, "Oops")
    wait(5, Wait.ABORT_WHEN_FALSE)
    eventPlayer.isCutsceneFinished = true

rule "do inner radio conversation cleanup":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.INNER_RADIO and eventPlayer.isCutsceneFinished

    eventPlayer.currentCutscene = Cutscene.NONE
    eventPlayer.isCutsceneFinished = false

    smallMessage(eventPlayer, "Press {0} to be the other guy".format(buttonString(Button.INTERACT)))
    eventPlayer.canBeTheOtherGuy = true
    if eventPlayer not in radioSmokePlayers:
        radioSmokePlayers.append(eventPlayer)
    unfreeze()
    showUi()
    eventPlayer.stopFacing()
    popAllEffectDeleteQueue()
    popAllTextDeleteQueue()

rule "do comedy routine":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.COMEDY and not eventPlayer.isCutsceneFinished

    freeze()
    smallMessage(eventPlayer, "You walk up on stage and everyone starts laughing")
    wait(5, Wait.ABORT_WHEN_FALSE)
    smallMessage(eventPlayer, "But you haven't even told any jokes yet!")
    wait(5, Wait.ABORT_WHEN_FALSE)
    smallMessage(eventPlayer, "You rip the mask off in disgust")
    wait(5, Wait.ABORT_WHEN_FALSE)
    smallMessage(eventPlayer, "Your employer tells you that's enough and waves you over")

    eventPlayer.isCutsceneFinished = true

rule "do comedy routine cleanup":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.COMEDY and eventPlayer.isCutsceneFinished

    eventPlayer.currentCutscene = Cutscene.NONE
    eventPlayer.isCutsceneFinished = false

    unfreeze()
    eventPlayer.equipped.remove(Equip.DISGUISE)
    manageEquips()

def doBruceContact():
    eventPlayer.pauseInteraction = true

    dvdLogoPlayers.remove(eventPlayer)
    wait(0.5)
    satControlPlayers.append(eventPlayer)

    createEffect(eventPlayer, Effect.ECHO_CLONING_SOUND, null, OUTSIDE_SAT_TO_WORLD(vect(0.9, 0.5, 0)), 50, EffectReeval.NONE)
    pushEffectDeleteQueue()

    eventPlayer.tStart = 0 # beam angle
    eventPlayer.tEnd = 0.1 # center sphere radius

    createBeam(eventPlayer, Beam.MERCY_BOOST, OUTSIDE_SAT_TO_WORLD(vect(0.1, 0.1, 0)), OUTSIDE_SAT_TO_WORLD(vect(0.1 + sin(eventPlayer.tStart), 0.1 + cos(eventPlayer.tStart) * 0.9, 0)), Color.TEAM_1, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    createBeam(eventPlayer, Beam.MERCY_BOOST, OUTSIDE_SAT_TO_WORLD(vect(1.7, 0.1, 0)), OUTSIDE_SAT_TO_WORLD(vect(1.7 - sin(eventPlayer.tStart), 0.1 + cos(eventPlayer.tStart) * 0.9, 0)), Color.TEAM_1, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    createBeam(eventPlayer, Beam.MERCY_BOOST, OUTSIDE_SAT_TO_WORLD(vect(0.1, 0.9, 0)), OUTSIDE_SAT_TO_WORLD(vect(0.1 + sin(eventPlayer.tStart), 0.9 - cos(eventPlayer.tStart) * 0.9, 0)), Color.TEAM_1, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    createBeam(eventPlayer, Beam.MERCY_BOOST, OUTSIDE_SAT_TO_WORLD(vect(1.7, 0.9, 0)), OUTSIDE_SAT_TO_WORLD(vect(1.7 - sin(eventPlayer.tStart), 0.9 - cos(eventPlayer.tStart) * 0.9, 0)), Color.TEAM_1, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()

    wait(1)
    chase(eventPlayer.tStart, 1.107, duration=3, ChaseReeval.NONE)

    wait(0.9)

    eventPlayer.tPos = 0
    createEffect(eventPlayer, Effect.SIGMA_GRAVITIC_FLUX_TARGET_SOUND, null, OUTSIDE_SAT_TO_WORLD(vect(0.9, 0.5, 0)), eventPlayer.tPos, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    chase(eventPlayer.tPos, 200, duration=4.1, ChaseReeval.NONE)

    wait(2.1)
    createEffect(eventPlayer, Effect.SPHERE, Color.WHITE, OUTSIDE_SAT_TO_WORLD(vect(0.9, 0.5, 0)), updateEveryTick(eventPlayer.tEnd), EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()

    wait(2)
    popEffectDeleteQueue()
    popEffectDeleteQueue()
    popEffectDeleteQueue()
    popEffectDeleteQueue()
    popEffectDeleteQueue()
    popEffectDeleteQueue()
    satControlPlayers.remove(eventPlayer)

    chase(eventPlayer.tEnd, 0, duration=0.3, ChaseReeval.NONE)
    playEffect(eventPlayer, DynamicEffect.MOIRA_FADE_DISAPPEAR_SOUND, null, OUTSIDE_SAT_TO_WORLD(vect(0.9, 0.5, 0)), 50)

    wait(0.3)
    popEffectDeleteQueue()

    stopChasingVariable(eventPlayer.tStart)
    stopChasingVariable(eventPlayer.tEnd)

    wait(2)
    smallMessage(eventPlayer, "You tell Bruce to expect an annoying little rat with a smug grin")

    eventPlayer.pauseInteraction = false

def doMoleLeaderInteractionStage1():
    freeze()

    eventPlayer.tPos = eventPlayer.getEyePosition() + eventPlayer.getFacingDirection()
    chase(eventPlayer.tPos, DOOR_24_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)
    eventPlayer.startFacing(directionTowards(eventPlayer.getEyePosition(), eventPlayer.tPos), 9999, Relativity.TO_WORLD, FacingReeval.DIRECTION_AND_TURN_RATE)

    CREATE_TEXT("Seek first the room beneath the outer wall.\nIf no one's there, return back to this hall.", DOOR_24_TEXT_LOCATION)
    wait(0.5)

    stopChasingVariable(eventPlayer.tPos)
    eventPlayer.stopFacing()
    unfreeze()

def doMoleLeaderInteractionStage2():
    freeze()

    eventPlayer.tPos = eventPlayer.getEyePosition() + eventPlayer.getFacingDirection()
    chase(eventPlayer.tPos, DOOR_24_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)
    eventPlayer.startFacing(directionTowards(eventPlayer.getEyePosition(), eventPlayer.tPos), 9999, Relativity.TO_WORLD, FacingReeval.DIRECTION_AND_TURN_RATE)

    CREATE_TEXT("Look next behind the old unused latrine.\nNot in the open, but the one less seen.", DOOR_24_TEXT_LOCATION)
    wait(0.5)

    stopChasingVariable(eventPlayer.tPos)
    eventPlayer.stopFacing()
    unfreeze()

def doMoleLeaderInteractionStage3():
    freeze()

    eventPlayer.tPos = eventPlayer.getEyePosition() + eventPlayer.getFacingDirection()
    chase(eventPlayer.tPos, DOOR_24_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)
    eventPlayer.startFacing(directionTowards(eventPlayer.getEyePosition(), eventPlayer.tPos), 9999, Relativity.TO_WORLD, FacingReeval.DIRECTION_AND_TURN_RATE)

    CREATE_TEXT("Beneath the two stacked cars you next should comb,\nalthough it can be hardly called a home.", DOOR_24_TEXT_LOCATION)
    wait(0.5)

    stopChasingVariable(eventPlayer.tPos)
    eventPlayer.stopFacing()
    unfreeze()

def doMoleLeaderInteractionStage4():
    freeze()

    eventPlayer.tPos = eventPlayer.getEyePosition() + eventPlayer.getFacingDirection()
    chase(eventPlayer.tPos, DOOR_24_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)
    eventPlayer.startFacing(directionTowards(eventPlayer.getEyePosition(), eventPlayer.tPos), 9999, Relativity.TO_WORLD, FacingReeval.DIRECTION_AND_TURN_RATE)

    CREATE_TEXT("The final place we know is by the cliff\nwhere sewage drains, you'll know it if you sniff.", DOOR_24_TEXT_LOCATION)
    wait(0.5)

    stopChasingVariable(eventPlayer.tPos)
    eventPlayer.stopFacing()
    unfreeze()

rule "do mole leader interaction":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.MOLE_LEADER_INTERACTION and not eventPlayer.isCutsceneFinished

    freeze()
    hideUi()

    eventPlayer.tPos = eventPlayer.getEyePosition() + eventPlayer.getFacingDirection()
    chase(eventPlayer.tPos, DOOR_24_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)

    eventPlayer.startFacing(directionTowards(eventPlayer.getEyePosition(), eventPlayer.tPos), 9999, Relativity.TO_WORLD, FacingReeval.DIRECTION_AND_TURN_RATE)
    CREATE_TEXT("Ahh...", DOOR_24_TEXT_LOCATION)
    wait(2, Wait.ABORT_WHEN_FALSE)
    CREATE_TEXT("You've found the thing we need to free ourselves", DOOR_24_TEXT_LOCATION)
    wait(4, Wait.ABORT_WHEN_FALSE)

    stopChasingVariable(eventPlayer.tPos)
    chase(eventPlayer.tPos, DOOR_21_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)
    CREATE_TEXT("from shackles long endured and sorrows wept.", DOOR_21_TEXT_LOCATION)
    wait(4, Wait.ABORT_WHEN_FALSE)

    stopChasingVariable(eventPlayer.tPos)
    chase(eventPlayer.tPos, DOOR_23_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)
    CREATE_TEXT("The walls around this city hem us in", DOOR_23_TEXT_LOCATION)
    wait(4, Wait.ABORT_WHEN_FALSE)

    stopChasingVariable(eventPlayer.tPos)
    chase(eventPlayer.tPos, DOOR_25_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)
    CREATE_TEXT("but we must not expose our secrets kept.", DOOR_25_TEXT_LOCATION)
    wait(4, Wait.ABORT_WHEN_FALSE)

    stopChasingVariable(eventPlayer.tPos)
    chase(eventPlayer.tPos, DOOR_24_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)
    CREATE_TEXT("And so we ask your aid in our endeavor:", DOOR_24_TEXT_LOCATION)
    wait(4, Wait.ABORT_WHEN_FALSE)

    stopChasingVariable(eventPlayer.tPos)
    chase(eventPlayer.tPos, DOOR_22_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)
    CREATE_TEXT("Bring this device outside the barren gate", DOOR_22_TEXT_LOCATION)
    wait(4, Wait.ABORT_WHEN_FALSE)

    stopChasingVariable(eventPlayer.tPos)
    chase(eventPlayer.tPos, DOOR_23_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)
    CREATE_TEXT("to one who hides amongst the dust with fervor-", DOOR_23_TEXT_LOCATION)
    wait(4, Wait.ABORT_WHEN_FALSE)

    stopChasingVariable(eventPlayer.tPos)
    chase(eventPlayer.tPos, DOOR_25_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)
    CREATE_TEXT("then we shall help you passages locate.", DOOR_25_TEXT_LOCATION)
    wait(4, Wait.ABORT_WHEN_FALSE)

    stopChasingVariable(eventPlayer.tPos)
    eventPlayer.isCutsceneFinished = true

rule "do mole leader interaction cleanup":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.MOLE_LEADER_INTERACTION and eventPlayer.isCutsceneFinished

    eventPlayer.currentCutscene = Cutscene.NONE
    eventPlayer.isCutsceneFinished = false

    eventPlayer.inventory.remove("Odd looking coin")
    GIVE_ITEM(MOLE_PERSON_DEVICE_NAME)

    popTextDeleteQueue()
    stopChasingVariable(eventPlayer.tPos)
    eventPlayer.stopFacing()
    showUi()
    unfreeze()

    doMoleLeaderInteractionStage1()

rule "do mole agent interaction":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.MOLE_AGENT_INTERACTION and not eventPlayer.isCutsceneFinished

    freeze()
    hideUi()
    eventPlayer.startFacing(directionTowards(eventPlayer.getEyePosition(), MOLE_PERSON_CLIFF_TEXT_LOCATION - vect(0, 0.3, 0)), 60, Relativity.TO_WORLD, FacingReeval.DIRECTION_AND_TURN_RATE)

    CREATE_TEXT("Eep! Is that what I think it is?", MOLE_PERSON_CLIFF_TEXT_LOCATION)
    wait(3, Wait.ABORT_WHEN_FALSE)

    CREATE_TEXT("I never thought I'd see it... squiz", MOLE_PERSON_CLIFF_TEXT_LOCATION)
    wait(3, Wait.ABORT_WHEN_FALSE)

    CREATE_TEXT("Sorry, I was never any good at the whole riddle thing.\n", MOLE_PERSON_CLIFF_TEXT_LOCATION)
    wait(3, Wait.ABORT_WHEN_FALSE)

    CREATE_TEXT("Sorry, I was never any good at the whole riddle thing.\nMaybe that's why they sent me out here.", MOLE_PERSON_CLIFF_TEXT_LOCATION)
    wait(5, Wait.ABORT_WHEN_FALSE)

    CREATE_TEXT("*Sigh*", MOLE_PERSON_CLIFF_TEXT_LOCATION)
    wait(2, Wait.ABORT_WHEN_FALSE)
    popTextDeleteQueue()
    wait(2, Wait.ABORT_WHEN_FALSE)

    CREATE_TEXT("It's nothing you need to worry about though.\nThanks for getting the device out of Junkertown.", MOLE_PERSON_CLIFF_TEXT_LOCATION)
    wait(6, Wait.ABORT_WHEN_FALSE)

    CREATE_TEXT("Tell your friend I'm sorry for knocking him out and stuffing\nhim into the safe.", MOLE_PERSON_CLIFF_TEXT_LOCATION)
    wait(5, Wait.ABORT_WHEN_FALSE)

    CREATE_TEXT("Gold's the only thing the Queen lets in the vault, so there\nwas no other way in.", MOLE_PERSON_CLIFF_TEXT_LOCATION)
    wait(5, Wait.ABORT_WHEN_FALSE)

    CREATE_TEXT("And that's where she was keeping the Device.", MOLE_PERSON_CLIFF_TEXT_LOCATION)
    wait(4, Wait.ABORT_WHEN_FALSE)

    popTextDeleteQueue()
    wait(2, Wait.ABORT_WHEN_FALSE)

    CREATE_TEXT("I'll let the others know you've delivered it.", MOLE_PERSON_CLIFF_TEXT_LOCATION)
    wait(4, Wait.ABORT_WHEN_FALSE)

    CREATE_TEXT("They'll get your friend where he needs to go.", MOLE_PERSON_CLIFF_TEXT_LOCATION)
    wait(4, Wait.ABORT_WHEN_FALSE)

    popTextDeleteQueue()

    eventPlayer.isCutsceneFinished = true

rule "do mole agent interaction cleanup":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.MOLE_AGENT_INTERACTION and eventPlayer.isCutsceneFinished

    eventPlayer.currentCutscene = Cutscene.NONE
    eventPlayer.isCutsceneFinished = false

    popTextDeleteQueue()
    unfreeze()
    showUi()
    eventPlayer.stopFacing()

rule "do ending cutscene":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.ENDING and not eventPlayer.isCutsceneFinished

    freeze()
    hideUi()

    eventPlayer.tPos = eventPlayer.getEyePosition() + eventPlayer.getFacingDirection()
    chase(eventPlayer.tPos, DOOR_24_TEXT_LOCATION, duration=0.5, ChaseReeval.DESTINATION_AND_DURATION)
    eventPlayer.startFacing(directionTowards(eventPlayer.getEyePosition(), eventPlayer.tPos), 9999, Relativity.TO_WORLD, FacingReeval.DIRECTION_AND_TURN_RATE)

    CREATE_TEXT("      A device, stolen\n Our people, liberated\nAnd you, the way out", DOOR_24_TEXT_LOCATION)
    wait(0.5, Wait.ABORT_WHEN_FALSE)

    stopChasingVariable(eventPlayer.tPos)
    wait(5, Wait.ABORT_WHEN_FALSE)
    popTextDeleteQueue()

    FADE_TO_BLACK()
    wait(3, Wait.ABORT_WHEN_FALSE)

    # walk along path
    eventPlayer.bezier = [
        vect(-37.02, 33.22, -123.09),
        vect(-38.33, 33.22, -121.57),
        vect(-39.65, 33.22, -120.05),
        vect(-41.69, 33.22, -118.41),
        vect(-43.72, 33.22, -116.78),
        vect(-46.47, 33.22, -115.02),
        vect(-49.22, 33.22, -113.27),
    ]
    eventPlayer.bezierDuration = 3
    eventPlayer.startCamera(BEZIER_POSITION, BEZIER_FACING, 0)
    doBezierArray()

    wait(0.5, Wait.ABORT_WHEN_FALSE)

    # look up at ladder
    eventPlayer.tPos = BEZIER_POSITION
    eventPlayer.tEnd = eventPlayer.tPos + vect(-0.84, 0, 0.54)

    eventPlayer.startCamera(eventPlayer.tPos, eventPlayer.tEnd, 0)
    chase(eventPlayer.tEnd, eventPlayer.tPos + vect(0, 10, -1), duration=3, ChaseReeval.NONE)
    wait(4, Wait.ABORT_WHEN_FALSE)
    stopChasingVariable(eventPlayer.tEnd)

    # climb ladder looking right
    eventPlayer.bezier = [
        vect(-49.22, 33.22, -113.27),
        vect(-48.90, 35.69, -112.71),
        vect(-48.58, 38.15, -112.15),
        vect(-48.46, 40.97, -111.93),
        vect(-48.34, 43.79, -111.71),
        vect(-48.41, 46.96, -111.83),
        vect(-48.53, 49.74, -112.06),
    ]
    eventPlayer.bezierDuration = 3
    eventPlayer.startCamera(BEZIER_POSITION, BEZIER_POSITION + vect(0.99, 0, -0.16), 10)
    doBezierArray()

    # climb ladder looking up
    eventPlayer.bezier = [
        vect(-48.53, 49.74, -112.06),
        vect(-48.65, 52.52, -112.29),
        vect(-48.83, 54.90, -112.63),
        vect(-49.20, 57.42, -113.32),
        vect(-49.58, 59.94, -114.01),
        vect(-50.16, 62.59, -115.05),
        vect(-50.73, 65.25, -116.09),
    ]
    eventPlayer.bezierDuration = 3
    eventPlayer.startCamera(BEZIER_POSITION, BEZIER_FACING, 3)
    doBezierArray()

    wait(0.5, Wait.ABORT_WHEN_FALSE)

    # look to right
    eventPlayer.tPos = vect(-50.73, 65.25, -116.09)
    eventPlayer.tEnd = eventPlayer.tPos + vect(-2.068, 8.620, -3.829)

    eventPlayer.startCamera(eventPlayer.tPos, eventPlayer.tEnd, 5)
    chase(eventPlayer.tEnd, vect(-50.73, 65.25, -116.09) + (vect(-48.03, 65.26, -116.94) - vect(-50.73, 65.25, -116.09)) * 2, duration=1.5, ChaseReeval.NONE)
    wait(2.5, Wait.ABORT_WHEN_FALSE)
    stopChasingVariable(eventPlayer.tEnd)

    # walk to jump off point
    chase(eventPlayer.tPos, vect(-48.03, 65.26, -116.94), duration=1, ChaseReeval.NONE)
    wait(1.5, Wait.ABORT_WHEN_FALSE)

    # look over city
    eventPlayer.tEnd = vect(-50.73, 65.25, -116.09) + (vect(-48.03, 65.26, -116.94) - vect(-50.73, 65.25, -116.09)) * 20
    eventPlayer.startCamera(vect(-48.03, 65.26, -116.94), eventPlayer.tEnd, 5)
    chase(eventPlayer.tEnd, vect(-30, 25, -36), duration=5, ChaseReeval.NONE)
    wait(6, Wait.ABORT_WHEN_FALSE)

    chase(eventPlayer.tEnd, vect(23, 33, -80), duration=3, ChaseReeval.NONE)
    wait(4, Wait.ABORT_WHEN_FALSE)

    chase(eventPlayer.tEnd, vect(-48.03, 65.26, -116.94) + vect(4.80, -30.51, 46.04), duration=2, ChaseReeval.NONE)
    wait(5, Wait.ABORT_WHEN_FALSE)

    # fly over walls
    eventPlayer.bezier = [
        vect(-48.03, 65.26, -116.94),
        vect(-47.62, 61.24, -110.82),
        vect(-47.22, 57.21, -104.71),
        vect(-42.88, 53.43, -99.76),
        vect(-38.54, 49.65, -94.81),
        vect(-30.27, 46.12, -91.03),
        vect(-19.10, 41.99, -93.56),
        vect(-7.94, 37.87, -96.08),
        vect(6.11, 33.16, -104.90),
        vect(23.30, 29.13, -109.77),
        vect(40.49, 25.09, -114.64),
        vect(60.81, 21.74, -115.56),
        vect(72.50, 19.39, -110.53),
        vect(84.19, 17.05, -105.50),
        vect(87.24, 15.72, -94.52),
        vect(97.07, 13.29, -81.67),
        vect(106.90, 10.86, -68.81),
        vect(123.52, 7.35, -54.09),
        vect(140.66, 4.80, -56.95),
        vect(157.80, 2.25, -59.81),
        vect(175.47, 0.67, -80.25),
        vect(193.15, -0.91, -100.69),
    ]
    eventPlayer.bezierDuration = 2
    eventPlayer.startCamera(BEZIER_POSITION, BEZIER_FACING, 0)
    doBezierArray()

    wait(2, Wait.ABORT_WHEN_FALSE)

    eventPlayer.isCutsceneFinished = true

def doCredits():
    eventPlayer.timeStarted = timer - eventPlayer.timeStarted

    FADE_TO_BLACK()
    wait(1)

    bigMessage(eventPlayer, "Achievement get! Escape artist: get out of Junkertown")
    playEffect(eventPlayer, DynamicEffect.SOMBRA_LOGO_SOUND, null, BLACK_SCREEN_POS, 200)
    eventPlayer.achievements.append(Achievement.ESCAPE_ARTIST)

    bigMessage([p for p in getAllPlayers() if p.currentCutscene == Cutscene.NONE and p != eventPlayer], "{0} has escaped Junkertown!".format(eventPlayer))

    eventPlayer.tText = " "
    if Achievement.MOST_WANTED in eventPlayer.achievements:
        eventPlayer.tText = "{0}{1} ".format(eventPlayer.tText, abilityIconString(Hero.MCCREE, Button.ULTIMATE))
    if Achievement.INFRASIGHT in eventPlayer.achievements:
        eventPlayer.tText = "{0}{1} ".format(eventPlayer.tText, abilityIconString(Hero.WIDOWMAKER, Button.ULTIMATE))
    if Achievement.PUZZLE_MASTER in eventPlayer.achievements:
        eventPlayer.tText = "{0}{1} ".format(eventPlayer.tText, abilityIconString(Hero.LUCIO, Button.JUMP))
    if Achievement.IN_A_HURRY in eventPlayer.achievements:
        eventPlayer.tText = "{0}{1} ".format(eventPlayer.tText, abilityIconString(Hero.SOLDIER, Button.ABILITY_1))
    if Achievement.MASTER_HACKER in eventPlayer.achievements:
        eventPlayer.tText = "{0}{1} ".format(eventPlayer.tText, abilityIconString(Hero.SOMBRA, Button.SECONDARY_FIRE))
    if Achievement.ESCAPE_ARTIST in eventPlayer.achievements:
        eventPlayer.tText = "{0}{1} ".format(eventPlayer.tText, abilityIconString(Hero.BAPTISTE, Button.ULTIMATE))

    wait(5)

    eventPlayer.tPos = -1.6
    CREATE_MOVABLE_TEXT("Thanks for playing", 0, 1, Color.WHITE)
    CREATE_MOVABLE_TEXT("Escape from", 3, 2, Color.WHITE)
    CREATE_MOVABLE_TEXT("Junkertown", 5, 4, rgb(236, 190, 82))
    CREATE_MOVABLE_TEXT("Created by EruIluvatar", 8.75, 0.8, Color.WHITE)
    CREATE_MOVABLE_TEXT("Testing by Shiner237", 9.25, 0.8, Color.WHITE)
    CREATE_MOVABLE_TEXT("Finish time: {0}m {1}s".format(floor(eventPlayer.timeStarted / 60), eventPlayer.timeStarted % 60), 13, 1.5, Color.WHITE if eventPlayer.timeStarted >= 420 else Color.GREEN)
    CREATE_MOVABLE_TEXT("You got {0} of {1} achievements".format(len(eventPlayer.achievements), Achievement.END_MARKER - 1), 16, 2, Color.PURPLE)
    CREATE_MOVABLE_TEXT(eventPlayer.tText, 17.5, 1.5, Color.PURPLE)

    chase(eventPlayer.tPos, 0.8, rate=0.3, ChaseReeval.NONE)
    wait(8)
    stopChasingVariable(eventPlayer.tPos)

    wait(3)
    CREATE_MOVABLE_TEXT("Press {0} to restart".format(buttonString(Button.INTERACT)), 21, 1.5, Color.WHITE)
    eventPlayer.hasFinishedGame = true

rule "do ending cutscene cleanup":
    @Event eachPlayer
    @Condition eventPlayer.currentCutscene == Cutscene.ENDING and eventPlayer.isCutsceneFinished

    eventPlayer.currentCutscene = Cutscene.NONE
    eventPlayer.isCutsceneFinished = false

    stopChasingVariable(eventPlayer.tPos)
    stopChasingVariable(eventPlayer.tEnd)
    doCredits()



# PASSIVE ONGOING

rule "Set spawns":
    @Event playerDied

    eventPlayer.resurrect()
    if eventPlayer.isJunk:
        eventPlayer.teleport(vect(-95.58, 12.17, -86.79))
        eventPlayer.setFacing(vect(0.21, 0.08, 0.97), Relativity.TO_WORLD)
    else:
        eventPlayer.teleport(vect(59.23, 9, -77.60))
        eventPlayer.setFacing(vect(-1, 0, 0), Relativity.TO_WORLD)

rule "Update junk position and direction":
    @Event eachPlayer
    @Condition eventPlayer.isJunk

    eventPlayer.junkPos = eventPlayer.getPosition()
    eventPlayer.junkFacing = eventPlayer.getFacingDirection()
    wait(0.16, Wait.ABORT_WHEN_FALSE)
    goto RULE_START

rule "Update hog position and direction":
    @Event eachPlayer
    @Condition eventPlayer.isHog

    eventPlayer.hogPos = eventPlayer.getPosition()
    eventPlayer.hogFacing = eventPlayer.getFacingDirection()
    wait(0.16, Wait.ABORT_WHEN_FALSE)
    goto RULE_START

#!define SPAWN_CLIP_RADIUS 1.3
#!define NEXT_TO(pos) (distance(eventPlayer.getEyePosition(), pos) < SPAWN_CLIP_RADIUS)

rule "Walk through spawn protections enable":
    @Event eachPlayer
    @Condition NEXT_TO(vect(-55.57, 9, -82.41)) or NEXT_TO(vect(-66.38, 11, -80.21))

    eventPlayer.disableEnvironmentCollision(false)

rule "Walk through spawn protections disable":
    @Event eachPlayer
    @Condition not NEXT_TO(vect(-55.57, 9, -82.41)) and not NEXT_TO(vect(-66.38, 11, -80.21))

    eventPlayer.enableEnvironmentCollision()

# requires that all ncps are at least 10m apart
rule "Hide npc text":
    @Event eachPlayer
    @Condition distance(eventPlayer, BRUCE_TEXT_LOCATION) > 5
    @Condition distance(eventPlayer, INSIDE_SMUGGLER_TEXT_LOCATION) > 5
    @Condition distance(eventPlayer, OUTSIDE_SMUGGLER_TEXT_LOCATION) > 5
    @Condition distance(eventPlayer, BARTENDER_TEXT_LOCATION) > 5
    @Condition distance(eventPlayer, SHADY_CHARACTER_TEXT_LOCATION) > 5
    @Condition distance(eventPlayer, vect(-49.37, 13.63, -86.90)) > 12 or eventPlayer.getPosition().y < 12.40
    @Condition distance(eventPlayer, MOLE_PERSON_ROOM_TEXT_LOCATION) > 16
    @Condition distance(eventPlayer, MOLE_PERSON_OUTHOUSE_TEXT_LOCATION) > 12
    @Condition distance(eventPlayer, MOLE_PERSON_CAR_TEXT_LOCATION) > 16
    @Condition distance(eventPlayer, MOLE_PERSON_CLIFF_TEXT_LOCATION) > 16

    popTextDeleteQueue()



# STATE CHANGES

rule "Poster achievement":
    @Event eachPlayer
    @Condition len(eventPlayer.postersFound) == 12

    bigMessage(eventPlayer, "Achievement get! Most wanted: find all the posters of you")
    playEffect(eventPlayer, DynamicEffect.SOMBRA_LOGO_SOUND, null, eventPlayer.getPosition(), 200)
    eventPlayer.achievements.append(Achievement.MOST_WANTED)
    manageAchievements()

rule "Inner lights reset":
    @Event eachPlayer
    @Condition distance(eventPlayer.getEyePosition(), vect(-111.73, 14.04, -137.34)) > 4 and not eventPlayer.powerSwitched

    if eventPlayer not in powerPanelPlayers[0]:
        powerPanelPlayers[0].append(eventPlayer)
    if eventPlayer not in powerPanelPlayers[1]:
        powerPanelPlayers[1].append(eventPlayer)
    if eventPlayer not in powerPanelPlayers[2]:
        powerPanelPlayers[2].append(eventPlayer)
    if eventPlayer not in powerPanelPlayers[3]:
        powerPanelPlayers[3].append(eventPlayer)
    if eventPlayer not in powerPanelPlayers[4]:
        powerPanelPlayers[4].append(eventPlayer)

rule "Lights on check":
    @Event eachPlayer
    @Condition eventPlayer.wiresConnected and eventPlayer.generatorOn and eventPlayer.powerSwitched

    eventPlayer.lightsOn = true
    playersInDark.remove(eventPlayer)
    playEffect(eventPlayer, DynamicEffect.SOMBRA_TRANSLOCATING_SOUND, null, eventPlayer.getPosition() - eventPlayer.getFacingDirection() * 0.1, 100)
    INCREMENT_STATE(Id.innerSwitch1)
    INCREMENT_STATE(Id.innerSwitch2)
    INCREMENT_STATE(Id.innerSwitch3)
    INCREMENT_STATE(Id.innerSwitch4)
    INCREMENT_STATE(Id.innerSwitch5)

rule "Set inner power box state":
    @Event eachPlayer
    @Condition eventPlayer.wiresConnected and eventPlayer.generatorOn

    SET_STATE(Id.innerPowerBox, 3)
    wait(3)
    smallMessage(eventPlayer, "If you could just get the power panel all green, maybe the lights would turn on")

rule "Add graphics warning text":
    @Event eachPlayer
    @Condition distance(eventPlayer, vect(-111.67, 13.65, -137.37)) < 4

    graphicsWarningPlayers.append(eventPlayer)

rule "Remove graphics warning text":
    @Event eachPlayer
    @Condition distance(eventPlayer, vect(-111.67, 13.65, -137.37)) >= 4

    graphicsWarningPlayers.remove(eventPlayer)

rule "Inner workbench minigame start":
    @Event eachPlayer
    @Condition eventPlayer.isDoingInnerMinigame

    createBeam(eventPlayer, Beam.GRAPPLE, updateEveryTick(INNER_MINIGAME_2D_TO_3D(vect(0.801, 0.451, 0))), updateEveryTick(INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(eventPlayer.innerMinigameValue1) * 0.422, 0.451 + sin(eventPlayer.innerMinigameValue1 * 2) * 0.15, 0))), null, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    pushEffectDeleteQueue()

    createBeam(eventPlayer, Beam.BRIGITTE_FLAIL_CHAIN, updateEveryTick(INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(eventPlayer.innerMinigameValue1) * 0.422 + sin(eventPlayer.innerMinigameValue2) * 0.2, 0.451 + cos(eventPlayer.innerMinigameValue2) * 0.2 + sin(eventPlayer.innerMinigameValue1 * 2) * 0.15, 0))), updateEveryTick(INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(eventPlayer.innerMinigameValue1) * 0.422 + sin(eventPlayer.innerMinigameValue2 + 2 * PI / 3) * 0.2, 0.451 + cos(eventPlayer.innerMinigameValue2 + 2 * PI / 3) * 0.2 + sin(eventPlayer.innerMinigameValue1 * 2) * 0.15, 0))), null, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    createBeam(eventPlayer, Beam.BRIGITTE_FLAIL_CHAIN, updateEveryTick(INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(eventPlayer.innerMinigameValue1) * 0.422 + sin(eventPlayer.innerMinigameValue2 + 2 * PI / 3) * 0.2, 0.451 + cos(eventPlayer.innerMinigameValue2 + 2 * PI / 3) * 0.2 + sin(eventPlayer.innerMinigameValue1 * 2) * 0.15, 0))), updateEveryTick(INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(eventPlayer.innerMinigameValue1) * 0.422 + sin(eventPlayer.innerMinigameValue2 + 4 * PI / 3) * 0.2, 0.451 + cos(eventPlayer.innerMinigameValue2 + 4 * PI / 3) * 0.2 + sin(eventPlayer.innerMinigameValue1 * 2) * 0.15, 0))), null, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    createBeam(eventPlayer, Beam.BRIGITTE_FLAIL_CHAIN, updateEveryTick(INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(eventPlayer.innerMinigameValue1) * 0.422 + sin(eventPlayer.innerMinigameValue2 + 4 * PI / 3) * 0.2, 0.451 + cos(eventPlayer.innerMinigameValue2 + 4 * PI / 3) * 0.2 + sin(eventPlayer.innerMinigameValue1 * 2) * 0.15, 0))), updateEveryTick(INNER_MINIGAME_2D_TO_3D(vect(0.801 + sin(eventPlayer.innerMinigameValue1) * 0.422 + sin(eventPlayer.innerMinigameValue2) * 0.2, 0.451 + cos(eventPlayer.innerMinigameValue2) * 0.2 + sin(eventPlayer.innerMinigameValue1 * 2) * 0.15, 0))), null, EffectReeval.VISIBILITY_POSITION_AND_RADIUS)
    pushEffectDeleteQueue()

rule "Stop inner workbench minigame controls":
    @Event eachPlayer
    @Condition eventPlayer.isDoingInnerMinigame and not eventPlayer.isHoldingButton(Button.PRIMARY_FIRE)

    eventPlayer.isHoldingInnerMinigameControl1 = false
    eventPlayer.isHoldingInnerMinigameControl2 = false

    stopChasingVariable(eventPlayer.innerMinigameValue1)
    stopChasingVariable(eventPlayer.innerMinigameValue2)

    if len(eventPlayer.effectDeleteQueue) > 4:
        destroyEffect(eventPlayer.effectDeleteQueue[4])
        del eventPlayer.effectDeleteQueue[4]

rule "Inner workbench minigame success":
    @Event eachPlayer
    @Condition abs(eventPlayer.innerMinigameValue1 % (2 * PI) - 3 * PI / 2) < 0.1 and abs(eventPlayer.innerMinigameValue2 % (2 * PI / 3) - PI / 6) < 0.05

    eventPlayer.hasFinishedInnerMinigame = true
    stopChasingVariable(eventPlayer.innerMinigameValue1)
    stopChasingVariable(eventPlayer.innerMinigameValue2)
    eventPlayer.innerMinigameValue1 = 3 * PI / 2
    eventPlayer.innerMinigameValue2 = PI / 6
    SET_STATE(Id.innerWorkbench, 1)
    eventPlayer.inventory.remove("Fiddly part")
    eventPlayer.inventory = "Reverse polarity fiddly part".concat(eventPlayer.inventory)
    smallMessage(eventPlayer, "You manage to reverse the polarity of the fiddly part")

rule "Inner workbench minigame stop":
    @Event eachPlayer
    @Condition eventPlayer.isDoingInnerMinigame and distance(eventPlayer, eventPlayer.innerWorkbench[Item.POINT]) > 3

    innerMinigamePlayers.remove(eventPlayer)
    popAllEffectDeleteQueue()
    wait(RACE_CONDITION_DELAY) # prevents flashes
    innerMinigameInitialState()

rule "Outer workbench minigame start":
    @Event eachPlayer
    @Condition eventPlayer.isDoingOuterMinigame

    createEffect(eventPlayer, Effect.SPHERE, outerMinigameColors[eventPlayer.outerMinigameColorState[1]], OUTER_MINIGAME_2D_TO_3D(vect(0.72 + sin(eventPlayer.outerMinigameRotState1) * 0.17 * 2 / 3, 0.08 + 0.17 * 2 + 0.17 / 3 + cos(eventPlayer.outerMinigameRotState1) * 0.17 * 2 / 3, 0)), OUTER_MINIGAME_BALL_SIZE, EffectReeval.POSITION_RADIUS_AND_COLOR)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, outerMinigameColors[eventPlayer.outerMinigameColorState[2]], OUTER_MINIGAME_2D_TO_3D(vect(0.72 + sin(eventPlayer.outerMinigameRotState1 + 4 * PI / 3) * 0.17 * 2 / 3, 0.08 + 0.17 * 2 + 0.17 / 3 + cos(eventPlayer.outerMinigameRotState1 + 4 * PI / 3) * 0.17 * 2 / 3, 0)), OUTER_MINIGAME_BALL_SIZE, EffectReeval.POSITION_RADIUS_AND_COLOR)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, outerMinigameColors[eventPlayer.outerMinigameColorState[3]], OUTER_MINIGAME_2D_TO_3D(vect(0.72 + sin(eventPlayer.outerMinigameRotState1 + 2 * PI / 3) * 0.17 * 2 / 3, 0.08 + 0.17 * 2 + 0.17 / 3 + cos(eventPlayer.outerMinigameRotState1 + 2 * PI / 3) * 0.17 * 2 / 3, 0)), OUTER_MINIGAME_BALL_SIZE, EffectReeval.POSITION_RADIUS_AND_COLOR)
    pushEffectDeleteQueue()

    createEffect(eventPlayer, Effect.SPHERE, outerMinigameColors[eventPlayer.outerMinigameColorState[4]], OUTER_MINIGAME_2D_TO_3D(vect(0.52 + sin(eventPlayer.outerMinigameRotState2) * 0.17 * 2 / 3, 0.08 + 0.17 / 3 + cos(eventPlayer.outerMinigameRotState2) * 0.17 * 2 / 3, 0)), OUTER_MINIGAME_BALL_SIZE, EffectReeval.POSITION_RADIUS_AND_COLOR)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, outerMinigameColors[eventPlayer.outerMinigameColorState[7]], OUTER_MINIGAME_2D_TO_3D(vect(0.52 + sin(eventPlayer.outerMinigameRotState2 + 4 * PI / 3) * 0.17 * 2 / 3, 0.08 + 0.17 / 3 + cos(eventPlayer.outerMinigameRotState2 + 4 * PI / 3) * 0.17 * 2 / 3, 0)), OUTER_MINIGAME_BALL_SIZE, EffectReeval.POSITION_RADIUS_AND_COLOR)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, outerMinigameColors[eventPlayer.outerMinigameColorState[8]], OUTER_MINIGAME_2D_TO_3D(vect(0.52 + sin(eventPlayer.outerMinigameRotState2 + 2 * PI / 3) * 0.17 * 2 / 3, 0.08 + 0.17 / 3 + cos(eventPlayer.outerMinigameRotState2 + 2 * PI / 3) * 0.17 * 2 / 3, 0)), OUTER_MINIGAME_BALL_SIZE, EffectReeval.POSITION_RADIUS_AND_COLOR)
    pushEffectDeleteQueue()

    createEffect(eventPlayer, Effect.SPHERE, outerMinigameColors[eventPlayer.outerMinigameColorState[6]], OUTER_MINIGAME_2D_TO_3D(vect(0.92 + sin(eventPlayer.outerMinigameRotState3) * 0.17 * 2 / 3, 0.08 + 0.17 / 3 + cos(eventPlayer.outerMinigameRotState3) * 0.17 * 2 / 3, 0)), OUTER_MINIGAME_BALL_SIZE, EffectReeval.POSITION_RADIUS_AND_COLOR)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, outerMinigameColors[eventPlayer.outerMinigameColorState[9]], OUTER_MINIGAME_2D_TO_3D(vect(0.92 + sin(eventPlayer.outerMinigameRotState3 + 4 * PI / 3) * 0.17 * 2 / 3, 0.08 + 0.17 / 3 + cos(eventPlayer.outerMinigameRotState3 + 4 * PI / 3) * 0.17 * 2 / 3, 0)), OUTER_MINIGAME_BALL_SIZE, EffectReeval.POSITION_RADIUS_AND_COLOR)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, outerMinigameColors[eventPlayer.outerMinigameColorState[10]], OUTER_MINIGAME_2D_TO_3D(vect(0.92 + sin(eventPlayer.outerMinigameRotState3 + 2 * PI / 3) * 0.17 * 2 / 3, 0.08 + 0.17 / 3 + cos(eventPlayer.outerMinigameRotState3 + 2 * PI / 3) * 0.17 * 2 / 3, 0)), OUTER_MINIGAME_BALL_SIZE, EffectReeval.POSITION_RADIUS_AND_COLOR)
    pushEffectDeleteQueue()

    createEffect(eventPlayer, Effect.SPHERE, outerMinigameColors[eventPlayer.outerMinigameColorState[5]], OUTER_MINIGAME_2D_TO_3D(vect(0.72, 0.08 + 0.17, 0)), OUTER_MINIGAME_BALL_SIZE, EffectReeval.POSITION_RADIUS_AND_COLOR)
    pushEffectDeleteQueue()

rule "Outer workbench minigame success":
    @Event eachPlayer
    @Condition all([e == 1 for e in eventPlayer.outerMinigameColorState])

    eventPlayer.hasFinishedOuterMinigame = true
    SET_STATE(Id.outerWorkbench, 1)
    eventPlayer.inventory.remove("Red battery")
    eventPlayer.inventory = "Black painted battery".concat(eventPlayer.inventory)
    smallMessage(eventPlayer, "You manage to change the color of the battery")

    if eventPlayer.outerMinigameMovesMade <= 5:
        wait(3) # wait for small message to disappear
        bigMessage(eventPlayer, "Achievement get! Puzzle master: solve the battery puzzle in 5 moves")
        playEffect(eventPlayer, DynamicEffect.SOMBRA_LOGO_SOUND, null, eventPlayer.getPosition(), 200)
        eventPlayer.achievements.append(Achievement.PUZZLE_MASTER)
        manageAchievements()

rule "Outer workbench minigame stop":
    @Event eachPlayer
    @Condition eventPlayer.isDoingOuterMinigame and distance(eventPlayer, eventPlayer.outerWorkbench[Item.POINT]) > 3

    outerMinigamePlayers.remove(eventPlayer)
    popAllEffectDeleteQueue()
    wait(RACE_CONDITION_DELAY) # prevents flashes
    outerMinigameInitialState()

rule "Barn workbench minigame start":
    @Event eachPlayer
    @Condition eventPlayer.isDoingBarnMinigame

    createEffect(eventPlayer, Effect.SPHERE, Color.BLACK, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[eventPlayer.barnMinigamePosState[0]]), 0.05, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, Color.BLACK, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[eventPlayer.barnMinigamePosState[1]]), 0.05, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, Color.BLACK, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[eventPlayer.barnMinigamePosState[2]]), 0.05, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, Color.BLACK, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[eventPlayer.barnMinigamePosState[3]]), 0.05, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, Color.BLUE, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[eventPlayer.barnMinigamePosState[4]]), 0.05, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, Color.BLUE, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[eventPlayer.barnMinigamePosState[5]]), 0.05, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, Color.BLUE, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[eventPlayer.barnMinigamePosState[6]]), 0.05, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()
    createEffect(eventPlayer, Effect.SPHERE, Color.BLUE, BARN_MINIGAME_2D_TO_3D(barnMinigamePoints[eventPlayer.barnMinigamePosState[7]]), 0.05, EffectReeval.POSITION_AND_RADIUS)
    pushEffectDeleteQueue()

rule "Barn workbench minigame success":
    @Event eachPlayer
    @Condition all([[0, 2, 2, 2, 2, 1, 1, 1, 1][index] == e for e, index in eventPlayer.barnMinigameColorState])

    eventPlayer.hasFinishedBarnMinigame = true
    SET_STATE(Id.barnWorkbench, 1)
    eventPlayer.inventory.remove("Right handed remote")
    eventPlayer.inventory = "Left handed remote".concat(eventPlayer.inventory)
    smallMessage(eventPlayer, "You pick up the remote with your left hand. Turns out remotes aren't handed.")

rule "Barn workbench minigame stop":
    @Event eachPlayer
    @Condition eventPlayer.isDoingBarnMinigame and distance(eventPlayer, eventPlayer.barnWorkbench[Item.POINT]) > 3

    barnMinigamePlayers.remove(eventPlayer)
    popAllEffectDeleteQueue()
    wait(RACE_CONDITION_DELAY) # prevents flashes
    barnMinigameInitialState()

# Can go outside
rule "Can go outside check":
    @Event eachPlayer
    @Condition eventPlayer.hasSunglasses
    @Condition eventPlayer.hasSunscreen

    eventPlayer.canGoOutside = true

# Bruce wings fix
rule "Bruce wings fix":
    @Event eachPlayer
    @Condition eventPlayer.bruceTimer == 60

    SET_STATE(Id.outerBruce, 9)
    stopChasingVariable(eventPlayer.bruceTimer)

# In a hurry achievement
rule "In a hurry achievement":
    @Event eachPlayer
    @Condition eventPlayer.timesBruceClicked == 60

    bigMessage(eventPlayer, "Achievement get! In a hurry: pester Bruce 60 times")
    playEffect(eventPlayer, DynamicEffect.SOMBRA_LOGO_SOUND, null, eventPlayer.getPosition(), 200)
    eventPlayer.achievements.append(Achievement.IN_A_HURRY)
    manageAchievements()



# CONTROLS

rule "Switch between heroes":
    @Event eachPlayer
    @Condition eventPlayer.canBeTheOtherGuy
    @Condition eventPlayer.isHoldingButton(Button.INTERACT) and not eventPlayer.pauseInteraction

    if eventPlayer.isJunk:
        eventPlayer.isJunk = false
        smallMessage(eventPlayer, "You are now the other guy")
        wait(RACE_CONDITION_DELAY)
        eventPlayer.junkInventory = eventPlayer.inventory
        eventPlayer.inventory = eventPlayer.hogInventory
        eventPlayer.junkEquipped = eventPlayer.equipped
        eventPlayer.equipped = eventPlayer.hogEquipped
        eventPlayer.startForcingHero(Hero.ROADHOG)
        eventPlayer.teleport(eventPlayer.hogPos)
        eventPlayer.setFacing(eventPlayer.hogFacing, Relativity.TO_WORLD)
        eventPlayer.isHog = true
        eventPlayer.preloadHero(Hero.ROADHOG)
    else:
        eventPlayer.isHog = false
        smallMessage(eventPlayer, "You stop being the other guy")
        wait(RACE_CONDITION_DELAY)
        eventPlayer.hogInventory = eventPlayer.inventory
        eventPlayer.inventory = eventPlayer.junkInventory
        eventPlayer.hogEquipped = eventPlayer.equipped
        eventPlayer.equipped = eventPlayer.junkEquipped
        eventPlayer.startForcingHero(Hero.JUNKRAT)
        eventPlayer.teleport(eventPlayer.junkPos)
        eventPlayer.setFacing(eventPlayer.junkFacing, Relativity.TO_WORLD)
        eventPlayer.isJunk = true
        eventPlayer.preloadHero(Hero.JUNKRAT)

    popAllTextDeleteQueue()
    manageEquips()

rule "Scroll inventory forwards":
    @Event eachPlayer
    @Condition eventPlayer.isHoldingButton(Button.ABILITY_2) and not eventPlayer.pauseInteraction

    if len(eventPlayer.inventory) > 1:
        eventPlayer.inventory = eventPlayer.inventory.last().concat(eventPlayer.inventory.slice(0, len(eventPlayer.inventory) - 1))

rule "Scroll inventory backwards":
    @Event eachPlayer
    @Condition eventPlayer.isHoldingButton(Button.ULTIMATE) and not eventPlayer.pauseInteraction

    if len(eventPlayer.inventory) > 1:
        eventPlayer.inventory.append(eventPlayer.inventory[0])
        del eventPlayer.inventory[0]

# Interact
def playGetItem():
    playEffect(eventPlayer, DynamicEffect.SOLDIER_SPRINT_START_SOUND, null, eventPlayer, 100)
    playEffect(eventPlayer, DynamicEffect.SOLDIER_SPRINT_START_SOUND, null, eventPlayer, 100)

def playNoItem():
    playEffect(eventPlayer, DynamicEffect.DVA_MICRO_MISSILES_EXPLOSION_SOUND, null, eventPlayer.getPosition() + eventPlayer.getFacingDirection(), 40)

#!define NOTHING_LEFT_MESSAGE "There's nothing left in there"

#!define SHOULD_INTERACT(item) (distance(eventPlayer.getEyePosition(), item[Item.POINT]) < INTERACT_DISTANCE and \
distance(item[Item.POINT], eventPlayer.getEyePosition() + eventPlayer.getFacingDirection() * distance(eventPlayer.getEyePosition(), item[Item.POINT])) < (item[Item.RADIUS]) and \
(not item[Item.NEEDS_LOS] or isInLoS(item[Item.POINT], eventPlayer.getEyePosition(), BarrierLos.PASS_THROUGH_BARRIERS)))

rule "Interact":
    @Event eachPlayer
    @Condition eventPlayer.isHoldingButton(Button.PRIMARY_FIRE) and not eventPlayer.pauseInteraction

    eventPlayer.tValue = [elem for elem in eventPlayer.itemPoints if SHOULD_INTERACT(elem)]

    if len(eventPlayer.tValue) == 0:
        return

    eventPlayer.tValue = eventPlayer.tValue[0]

    if eventPlayer.tValue[Item.REMOVE_ON_STATE] > 0 and eventPlayer.tValue[Item.STATE] >= eventPlayer.tValue[Item.REMOVE_ON_STATE]:
        return

    eventPlayer.tIndex = eventPlayer.itemIds.index(eventPlayer.tValue[Item.ID])

    if eventPlayer.tValue[Item.STATE] < len(eventPlayer.tValue[Item.MESSAGE]):
        eventPlayer.tText = eventPlayer.tValue[Item.MESSAGE][eventPlayer.tValue[Item.STATE]]
        if eventPlayer.tText:
            smallMessage(eventPlayer, eventPlayer.tText)

        if eventPlayer.tValue[Item.ITEM_NAME]:
            if eventPlayer.tValue[Item.PLAY_GET_SOUND]:
                playGetItem()
            GIVE_ITEM(eventPlayer.tValue[Item.ITEM_NAME])
            eventPlayer.tValue[Item.STATE]++
            eventPlayer.itemPoints[eventPlayer.tIndex] = eventPlayer.tValue
        else:
            if eventPlayer.tValue[Item.PLAY_GET_SOUND]:
                playNoItem()
    elif eventPlayer.tValue[Item.PLAY_GET_SOUND]:
        playNoItem()
        smallMessage(eventPlayer, NOTHING_LEFT_MESSAGE)

    # special interactions

    if eventPlayer.isJunk:
        if eventPlayer.wiresConnected and eventPlayer.generatorOn and not eventPlayer.lightsOn:
            if eventPlayer.tValue[Item.ID] == Id.innerSwitch1:
                TOGGLE_POWER_SWITCH(4)
                TOGGLE_POWER_SWITCH(0)
                TOGGLE_POWER_SWITCH(1)
                makeSwitchNoise()
                checkPowerPanel()
            elif eventPlayer.tValue[Item.ID] == Id.innerSwitch2:
                TOGGLE_POWER_SWITCH(0)
                TOGGLE_POWER_SWITCH(1)
                TOGGLE_POWER_SWITCH(2)
                makeSwitchNoise()
                checkPowerPanel()
            elif eventPlayer.tValue[Item.ID] == Id.innerSwitch3:
                TOGGLE_POWER_SWITCH(1)
                TOGGLE_POWER_SWITCH(2)
                TOGGLE_POWER_SWITCH(3)
                makeSwitchNoise()
                checkPowerPanel()
            elif eventPlayer.tValue[Item.ID] == Id.innerSwitch4:
                TOGGLE_POWER_SWITCH(2)
                TOGGLE_POWER_SWITCH(3)
                TOGGLE_POWER_SWITCH(4)
                makeSwitchNoise()
                checkPowerPanel()
            elif eventPlayer.tValue[Item.ID] == Id.innerSwitch5:
                TOGGLE_POWER_SWITCH(3)
                TOGGLE_POWER_SWITCH(4)
                TOGGLE_POWER_SWITCH(0)
                makeSwitchNoise()
                checkPowerPanel()

        if eventPlayer.tValue[Item.ID] == Id.tutSafe2:
            tutorialTextPlayers[TutorialText.INTERACT].remove(eventPlayer)
            eventPlayer.molePersonRiddleLine = 0
        elif eventPlayer.tValue[Item.ID] == Id.tutSafe5:
            if eventPlayer.tValue[Item.STATE] == 0:
                tutorialTextPlayers[TutorialText.INTERACT].remove(eventPlayer)
                tutorialTextPlayers[TutorialText.INV_CYCLE].append(eventPlayer)
                tutorialTextPlayers[TutorialText.APPLY_ITEM].append(eventPlayer)
                eventPlayer.inventory.append("Rusty key")
                eventPlayer.tValue[Item.STATE]++
                eventPlayer.itemPoints[eventPlayer.tIndex] = eventPlayer.tValue
        elif eventPlayer.tValue[Item.ID] == Id.innerPowerDistributor:
            if not eventPlayer.wiresConnected and not eventPlayer.generatorOn:
                smallMessage(eventPlayer, "This power distribution unit sends power from the generator to the panel above")
            elif not eventPlayer.wiresConnected:
                smallMessage(eventPlayer, "The generator is on, but it's not connected to anything yet")
            elif not eventPlayer.generatorOn:
                smallMessage(eventPlayer, "The generator is connected, but it's not on")
            else:
                if eventPlayer.lightsOn:
                    smallMessage(eventPlayer, "The generator is sending power through here to keep the lights on")
                else:
                    smallMessage(eventPlayer, "Power is being sent from the generator to the panel above, but not all the lights are green")
        elif eventPlayer.tValue[Item.ID] == Id.innerPowerBox:
            if not eventPlayer.wiresConnected or not eventPlayer.generatorOn:
                if eventPlayer.tValue[Item.STATE] < 2:
                    eventPlayer.tValue[Item.STATE]++
                    eventPlayer.itemPoints[eventPlayer.tIndex] = eventPlayer.tValue
            elif not eventPlayer.powerSwitched:
                smallMessage(eventPlayer, "There's power, so the switches next to these red lights should work")
            else:
                smallMessage(eventPlayer, "The pretty green lights make you giddy")
        elif eventPlayer.tValue[Item.ID] == Id.innerGenerator:
            if eventPlayer.tValue[Item.STATE] == 0:
                eventPlayer.tValue[Item.STATE]++
                eventPlayer.itemPoints[eventPlayer.tIndex] = eventPlayer.tValue
        elif eventPlayer.tValue[Item.ID] == Id.innerRadio:
            eventPlayer.hasFoundInnerRadio = true
        elif eventPlayer.tValue[Item.ID] == Id.innerWorkbench and not eventPlayer.isDoingInnerMinigame:
            if eventPlayer.tValue[Item.STATE] == 0:
                smallMessage(eventPlayer, "The workbench has a label that says \"polarity reversificator\" on it")
            else:
                smallMessage(eventPlayer, "The workbench has outlived its usefulness")
        elif eventPlayer.tValue[Item.ID] == Id.innerMinigameControl1 and eventPlayer.isDoingInnerMinigame and not eventPlayer.hasFinishedInnerMinigame:
            eventPlayer.isHoldingInnerMinigameControl1 = true
            chase(eventPlayer.innerMinigameValue1, INF, rate=1, ChaseReeval.NONE)
            chase(eventPlayer.innerMinigameValue2, INF, rate=1.8, ChaseReeval.NONE)
            createEffect(eventPlayer, Effect.LUCIO_SOUND_BARRIER_PROTECTED_SOUND, null, eventPlayer.getPosition(), 200, EffectReeval.NONE)
            pushEffectDeleteQueue()
        elif eventPlayer.tValue[Item.ID] == Id.innerMinigameControl2 and eventPlayer.isDoingInnerMinigame and not eventPlayer.hasFinishedInnerMinigame:
            eventPlayer.isHoldingInnerMinigameControl2 = true
            chase(eventPlayer.innerMinigameValue1, INF, rate=1.8, ChaseReeval.NONE)
            chase(eventPlayer.innerMinigameValue2, INF, rate=1, ChaseReeval.NONE)
            createEffect(eventPlayer, Effect.WRECKING_BALL_PILEDRIVER_FIRE_SOUND, null, eventPlayer.getPosition(), 200, EffectReeval.NONE)
            pushEffectDeleteQueue()
        elif eventPlayer.tValue[Item.ID] == Id.innerMinigameSkip and eventPlayer.isDoingInnerMinigame and not eventPlayer.hasFinishedInnerMinigame:
            eventPlayer.innerMinigameValue1 = 3 * PI / 2
            eventPlayer.innerMinigameValue2 = PI / 6
        elif eventPlayer.tValue[Item.ID] == Id.innerSunglasses:
            if Equip.SUNGLASES not in eventPlayer.equipped:
                INCREMENT_STATE(Id.innerSunglasses)
                eventPlayer.hasSunglasses = true
                eventPlayer.equipped.append(Equip.SUNGLASES)
                manageEquips()
                if not eventPlayer.outsideMapFound:
                    wait(3) # wait for small message to disappear
                    bigMessage(eventPlayer, "Achievement get! Infrasight: find the sunglasses without using the map")
                    playEffect(eventPlayer, DynamicEffect.SOMBRA_LOGO_SOUND, null, eventPlayer.getPosition(), 200)
                    eventPlayer.achievements.append(Achievement.INFRASIGHT)
                    # wait(15)
                    manageAchievements()
        elif eventPlayer.tValue[Item.STATE] == 0 and (eventPlayer.tValue[Item.ID] == Id.innerValve1 or eventPlayer.tValue[Item.ID] == Id.innerValve2 or eventPlayer.tValue[Item.ID] == Id.innerValve3):
            eventPlayer.tValue[Item.STATE]++
            eventPlayer.itemPoints[eventPlayer.tIndex] = eventPlayer.tValue
            eventPlayer.numVentsClosed++
            playEffect(eventPlayer, DynamicEffect.DOOMFIST_RISING_UPPERCUT_IMPACT_SOUND, null, eventPlayer.tValue[Item.POINT], 100)
            if eventPlayer.numVentsClosed == 1:
                smallMessage(eventPlayer, "You twist the valve shut and hear the airflow decrease")
            elif eventPlayer.numVentsClosed == 2:
                smallMessage(eventPlayer, "You twist the valve shut and hear the airflow slow to a trickle")
            else:
                smallMessage(eventPlayer, "You twist the valve shut and hear the air stop entirely")
        elif eventPlayer.tValue[Item.ID] == Id.innerVent3:
            if eventPlayer.tValue[Item.STATE] == 0:
                if eventPlayer.numVentsClosed == 3:
                    eventPlayer.tValue[Item.STATE]++
                    eventPlayer.itemPoints[eventPlayer.tIndex] = eventPlayer.tValue
                    smallMessage(eventPlayer, "You carefully reach your hand in and extract a dusty key")
                    GIVE_ITEM("Vent key")
                else:
                    smallMessage(eventPlayer, "There's something in the back, but the blades are spinning too fast to grab it")
            else:
                smallMessage(eventPlayer, NOTHING_LEFT_MESSAGE)
        elif eventPlayer.tValue[Item.ID] == Id.outerBruce:
            if eventPlayer.tValue[Item.STATE] == 0:
                CREATE_TEXT("You want me to help you? I don't even know who you are.", BRUCE_TEXT_LOCATION)
            elif eventPlayer.tValue[Item.STATE] == 1:
                CREATE_TEXT("Alright, since you're a friend of Roadhog, I guess I can help you out.\nI'll need you to bring me three things though:\n- a black battery\n- some purified water\n- and a bottle of strong alcohol.", BRUCE_TEXT_LOCATION)
            elif eventPlayer.tValue[Item.STATE] < 8:
                eventPlayer.tText = "You still need to bring me:"
                if eventPlayer.tValue[Item.STATE] % 2 == 1:
                    eventPlayer.tText = "{0}\n- a black battery".format(eventPlayer.tText)
                if (eventPlayer.tValue[Item.STATE] - 1) % 4 < 2:
                    eventPlayer.tText = "{0}\n- some purified water".format(eventPlayer.tText)
                if eventPlayer.tValue[Item.STATE] <= 4:
                    eventPlayer.tText = "{0}\n- a bottle of strong alcohol".format(eventPlayer.tText)
                CREATE_TEXT(eventPlayer.tText, BRUCE_TEXT_LOCATION)
            elif eventPlayer.tValue[Item.STATE] == 8:
                CREATE_TEXT("Don't be impatient, it's only been {0} seconds".format(floor(eventPlayer.bruceTimer)), BRUCE_TEXT_LOCATION)
                eventPlayer.timesBruceClicked++
            elif eventPlayer.tValue[Item.STATE] == 9:
                if Equip.WINGS not in eventPlayer.equipped:
                    CREATE_TEXT("Alright, the wings are done. You're going to have to find a high place to use\nthem though. I've heard there are secret tunnels that lead up to the dome. The\nbartender might know more.", BRUCE_TEXT_LOCATION)
                    eventPlayer.equipped.append(Equip.WINGS)
                    manageEquips()
                else:
                    CREATE_TEXT("You're going to have to find a high place to use your wings. I've heard there\nare secret tunnels that lead up to the dome. The bartender might know more.", BRUCE_TEXT_LOCATION)
                SET_STATE(Id.outerBartender, 2)
        elif eventPlayer.tValue[Item.ID] == Id.outerSmuggler:
            if not eventPlayer.canUtilizeSmuggler:
                CREATE_TEXT("Welcome to Tracer's Delivery Service!\nWe deliver anything to anyone anywhere!\nAnd we'll even deliver stuff from others to you!\nLifetime membership is only {0} dollars!".format(SMUGGLER_COST), INSIDE_SMUGGLER_TEXT_LOCATION)
            else:
                if eventPlayer.insideSmugglerItem:
                    insideMailTextPlayers.remove(eventPlayer)
                    CREATE_TEXT("Looks like we have something for you!", INSIDE_SMUGGLER_TEXT_LOCATION)
                    eventPlayer.tText = eventPlayer.insideSmugglerItem
                    smallMessage(eventPlayer, "Received {0}".format(eventPlayer.tText))
                    GIVE_ITEM(eventPlayer.insideSmugglerItem)
                    eventPlayer.insideSmugglerItem = null
                else:
                    CREATE_TEXT("Sorry, nothing for you", INSIDE_SMUGGLER_TEXT_LOCATION)
        elif eventPlayer.tValue[Item.ID] == Id.outerBartender:
            if eventPlayer.tValue[Item.STATE] == 0:
                CREATE_TEXT("Drinks round here cost {0} bucks. But just for you,\nI'll make an exception and only charge ya {1}.".format(ALCOHOL_COST / 2, ALCOHOL_COST), BARTENDER_TEXT_LOCATION)
            elif eventPlayer.tValue[Item.STATE] == 1:
                CREATE_TEXT("You've cleaned me right out of liquor.", BARTENDER_TEXT_LOCATION)
            else:
                CREATE_TEXT("So you're looking for the mole people, huh. I've heard they can be found in the\nrooms above the Chinese place. They all tend to talk in riddles though.", BARTENDER_TEXT_LOCATION)
        elif eventPlayer.tValue[Item.ID] == Id.outerShadyCharacter:
            if eventPlayer.tValue[Item.STATE] == 0:
                CREATE_TEXT("Hey, I'm looking for someone to embarrass themselves on stage,\nand you look like someone who doesn't know what that word means.\n\nGo up to the mic and do something silly, I'll pay you in cash.", SHADY_CHARACTER_TEXT_LOCATION)
                SET_STATE(Id.outerMic, 1)
            elif eventPlayer.tValue[Item.STATE] == 1:
                CREATE_TEXT("You certainly have a face made for radio.\n\nAlright, you've earned your pay.", SHADY_CHARACTER_TEXT_LOCATION)
                GIVE_ITEM("{0} dollars".format(ALCOHOL_COST + SMUGGLER_COST))
                eventPlayer.money = ALCOHOL_COST + SMUGGLER_COST
                eventPlayer.tValue[Item.STATE]++
                SAVE_STATE(eventPlayer.tValue)
            else:
                CREATE_TEXT("Did you know there's legends of a hidden race in Junkertown?\nSome say they go around stealing small objects, hiding things in vents.\nEh- but it's probably nothing right?", SHADY_CHARACTER_TEXT_LOCATION)
        elif eventPlayer.tValue[Item.ID] == Id.outerToolbox1:
            if eventPlayer.tValue[Item.STATE] == 0:
                eventPlayer.equipped.append(Equip.DISGUISE)
                eventPlayer.tValue[Item.STATE]++
                SAVE_STATE(eventPlayer.tValue)
                manageEquips()
        elif eventPlayer.tValue[Item.ID] == Id.outerWorkbench and not eventPlayer.isDoingOuterMinigame:
            if eventPlayer.tValue[Item.STATE] == 0:
                smallMessage(eventPlayer, "The workbench has a number of paints left on it")
            else:
                smallMessage(eventPlayer, "The workbench has outlived its usefulness")
        elif Id.outerMinigameDot1 <= eventPlayer.tValue[Item.ID] and eventPlayer.tValue[Item.ID] <= Id.outerMinigameSkip and eventPlayer.isDoingOuterMinigame and not eventPlayer.hasFinishedOuterMinigame:
            eventPlayer.outerMinigameMovesMade++
            if eventPlayer.tValue[Item.ID] == Id.outerMinigameDot1:
                playEffect(eventPlayer, DynamicEffect.BAPTISTE_BIOTIC_LAUNCHER_EXPLOSION_SOUND, null, eventPlayer, 30)
                OUTER_MINIGAME_TOGGLE(1)
                OUTER_MINIGAME_TOGGLE(2)
                OUTER_MINIGAME_TOGGLE(3)
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameDot2:
                playEffect(eventPlayer, DynamicEffect.BAPTISTE_BIOTIC_LAUNCHER_EXPLOSION_SOUND, null, eventPlayer, 30)
                OUTER_MINIGAME_TOGGLE(1)
                OUTER_MINIGAME_TOGGLE(2)
                OUTER_MINIGAME_TOGGLE(3)
                OUTER_MINIGAME_TOGGLE(4)
                OUTER_MINIGAME_TOGGLE(5)
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameDot3:
                playEffect(eventPlayer, DynamicEffect.BAPTISTE_BIOTIC_LAUNCHER_EXPLOSION_SOUND, null, eventPlayer, 30)
                OUTER_MINIGAME_TOGGLE(1)
                OUTER_MINIGAME_TOGGLE(2)
                OUTER_MINIGAME_TOGGLE(3)
                OUTER_MINIGAME_TOGGLE(5)
                OUTER_MINIGAME_TOGGLE(6)
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameDot4:
                playEffect(eventPlayer, DynamicEffect.BAPTISTE_BIOTIC_LAUNCHER_EXPLOSION_SOUND, null, eventPlayer, 30)
                OUTER_MINIGAME_TOGGLE(2)
                OUTER_MINIGAME_TOGGLE(4)
                OUTER_MINIGAME_TOGGLE(5)
                OUTER_MINIGAME_TOGGLE(7)
                OUTER_MINIGAME_TOGGLE(8)
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameDot5:
                playEffect(eventPlayer, DynamicEffect.EXPLOSION_SOUND, null, eventPlayer, 30)
                eventPlayer.outerMinigameMovesMade--
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameDot6:
                playEffect(eventPlayer, DynamicEffect.BAPTISTE_BIOTIC_LAUNCHER_EXPLOSION_SOUND, null, eventPlayer, 30)
                OUTER_MINIGAME_TOGGLE(3)
                OUTER_MINIGAME_TOGGLE(5)
                OUTER_MINIGAME_TOGGLE(6)
                OUTER_MINIGAME_TOGGLE(9)
                OUTER_MINIGAME_TOGGLE(10)
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameDot7:
                playEffect(eventPlayer, DynamicEffect.BAPTISTE_BIOTIC_LAUNCHER_EXPLOSION_SOUND, null, eventPlayer, 30)
                OUTER_MINIGAME_TOGGLE(4)
                OUTER_MINIGAME_TOGGLE(7)
                OUTER_MINIGAME_TOGGLE(8)
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameDot8:
                playEffect(eventPlayer, DynamicEffect.BAPTISTE_BIOTIC_LAUNCHER_EXPLOSION_SOUND, null, eventPlayer, 30)
                OUTER_MINIGAME_TOGGLE(4)
                OUTER_MINIGAME_TOGGLE(5)
                OUTER_MINIGAME_TOGGLE(7)
                OUTER_MINIGAME_TOGGLE(8)
                OUTER_MINIGAME_TOGGLE(9)
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameDot9:
                playEffect(eventPlayer, DynamicEffect.BAPTISTE_BIOTIC_LAUNCHER_EXPLOSION_SOUND, null, eventPlayer, 30)
                OUTER_MINIGAME_TOGGLE(5)
                OUTER_MINIGAME_TOGGLE(6)
                OUTER_MINIGAME_TOGGLE(8)
                OUTER_MINIGAME_TOGGLE(9)
                OUTER_MINIGAME_TOGGLE(10)
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameDot10:
                playEffect(eventPlayer, DynamicEffect.BAPTISTE_BIOTIC_LAUNCHER_EXPLOSION_SOUND, null, eventPlayer, 30)
                OUTER_MINIGAME_TOGGLE(6)
                OUTER_MINIGAME_TOGGLE(9)
                OUTER_MINIGAME_TOGGLE(10)
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameRotate1:
                eventPlayer.tPos = 1
                outerMinigameClick()
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameRotate2:
                eventPlayer.tPos = 2
                outerMinigameClick()
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameRotate3:
                eventPlayer.tPos = 3
                outerMinigameClick()
            elif eventPlayer.tValue[Item.ID] == Id.outerMinigameSkip:
                eventPlayer.outerMinigameColorState = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
                eventPlayer.outerMinigameMovesMade = INF
            else:
                eventPlayer.outerMinigameMovesMade--
        elif eventPlayer.tValue[Item.ID] == Id.outerMic:
            if eventPlayer.tValue[Item.STATE] == 0:
                smallMessage(eventPlayer, "You don't know why you would waste any time on this")
            elif eventPlayer.tValue[Item.STATE] == 1:
                if Equip.DISGUISE in eventPlayer.equipped:
                    eventPlayer.tValue[Item.STATE]++
                    eventPlayer.itemPoints[eventPlayer.tIndex] = eventPlayer.tValue
                    SET_STATE(Id.outerShadyCharacter, 1)

                    eventPlayer.currentCutscene = Cutscene.COMEDY
                else:
                    smallMessage(eventPlayer, "You couldn't possible be silly without a hilarious costume")
            else:
                smallMessage(eventPlayer, "You're done with comedy for today")
        elif Id.outerDoor21 <= eventPlayer.tValue[Item.ID] and eventPlayer.tValue[Item.ID] <= Id.outerDoor25:
            if Equip.WINGS not in eventPlayer.equipped:
                CREATE_TEXT("You don't look like you're ready yet.\nCome back when you can soar.", eventPlayer.tValue[Item.POINT] + vect(0, 0.6, 0))
            else:
                if "Odd looking coin" in eventPlayer.inventory:
                    CREATE_TEXT(moleLeaderRiddle[eventPlayer.molePersonRiddleLine], eventPlayer.tValue[Item.POINT] + vect(0, 0.6, 0))
                    eventPlayer.molePersonRiddleLine = (eventPlayer.molePersonRiddleLine + 1) % len(moleLeaderRiddle)
                elif eventPlayer.molePersonInteractStage == 0:
                    doMoleLeaderInteractionStage1()
                elif eventPlayer.molePersonInteractStage == 1:
                    doMoleLeaderInteractionStage2()
                elif eventPlayer.molePersonInteractStage == 2:
                    doMoleLeaderInteractionStage3()
                elif eventPlayer.molePersonInteractStage == 3:
                    doMoleLeaderInteractionStage4()
                elif eventPlayer.molePersonInteractStage == 4:
                    eventPlayer.currentCutscene = Cutscene.ENDING
                else:
                    CREATE_TEXT(moleCoinRiddle[eventPlayer.molePersonRiddleLine], eventPlayer.tValue[Item.POINT] + vect(0, 0.6, 0))
                    eventPlayer.molePersonRiddleLine = (eventPlayer.molePersonRiddleLine + 1) % len(moleCoinRiddle)
        elif eventPlayer.tValue[Item.ID] == Id.outerBinoculars:
            freeze()
            hideUi()

            eventPlayer.startCamera(vect(-37.04, 14.25, -59.71) + vect(0.653, -0.107, 0.749) * 530, vect(-37.04, 14.25, -59.71) + vect(0.653, -0.107, 0.749) * 531, 0)
            wait(5)
            eventPlayer.stopCamera()

            if Achievement.MASTER_HACKER not in eventPlayer.achievements:
                bigMessage(eventPlayer, "Achievement get! Master hacker: glitch through the walls and enjoy the view")
                wait(RACE_CONDITION_DELAY)
                playEffect(eventPlayer, DynamicEffect.SOMBRA_LOGO_SOUND, null, eventPlayer.getPosition(), 200)
                eventPlayer.achievements.append(Achievement.MASTER_HACKER)
                manageAchievements()

            unfreeze()
            showUi()

    if eventPlayer.isHog:
        if eventPlayer.tValue[Item.ID] == Id.barnSwitch1:
            eventPlayer.tPos = 10.656
            toggleBarnSwitch()
            ENTER_BARN_DIGIT(1)
            checkBarnCode()
        elif eventPlayer.tValue[Item.ID] == Id.barnSwitch2:
            eventPlayer.tPos = 10.581
            toggleBarnSwitch()
            ENTER_BARN_DIGIT(2)
            checkBarnCode()
        elif eventPlayer.tValue[Item.ID] == Id.barnSwitch3:
            eventPlayer.tPos = 10.509
            toggleBarnSwitch()
            ENTER_BARN_DIGIT(3)
            checkBarnCode()
        elif eventPlayer.tValue[Item.ID] == Id.barnSwitch4:
            eventPlayer.tPos = 10.425
            toggleBarnSwitch()
            ENTER_BARN_DIGIT(4)
            checkBarnCode()
        elif eventPlayer.tValue[Item.ID] == Id.barnSwitch5:
            eventPlayer.tPos = 10.346
            toggleBarnSwitch()
            ENTER_BARN_DIGIT(5)
            checkBarnCode()
        elif eventPlayer.tValue[Item.ID] == Id.barnPaperClue1:
            smallMessage(eventPlayer, "{0} {1} _ _ _".format(eventPlayer.barnCode[0], eventPlayer.barnCode[1]))
        elif eventPlayer.tValue[Item.ID] == Id.barnPaperClue2:
            smallMessage(eventPlayer, "_ _ _ {0} {1}".format(eventPlayer.barnCode[3], eventPlayer.barnCode[4]))
        elif eventPlayer.tValue[Item.ID] == Id.barnBike:
            smallMessage(eventPlayer, "There's some numbers scratched in the paint, but you can only make out the middle digit: {0}".format(eventPlayer.barnCode[2]))
        elif eventPlayer.tValue[Item.ID] == Id.barnSafe2:
            if Equip.LITTLE_BUDDY in eventPlayer.equipped:
                smallMessage(eventPlayer, "You've already secured your little buddy")
            else:
                eventPlayer.equipped.append(Equip.LITTLE_BUDDY)
                manageEquips()
                eventPlayer.tValue[Item.STATE]++
                eventPlayer.itemPoints[eventPlayer.tIndex] = eventPlayer.tValue
            eventPlayer.canLeaveBarn = true
        elif eventPlayer.tValue[Item.ID] == Id.barnWorkbench and not eventPlayer.isDoingBarnMinigame:
            if eventPlayer.tValue[Item.STATE] == 0:
                smallMessage(eventPlayer, "The workbench features a prominently placed mirror")
            else:
                smallMessage(eventPlayer, "The workbench has outlived its usefulness")
        elif Id.barnMinigameDot0 <= eventPlayer.tValue[Item.ID] and eventPlayer.tValue[Item.ID] <= Id.barnMinigameSkip and eventPlayer.isDoingBarnMinigame and not eventPlayer.hasFinishedBarnMinigame:
            if eventPlayer.tValue[Item.ID] == Id.barnMinigameDot0:
                eventPlayer.tPos = 0
                barnMinigameClick()
            elif eventPlayer.tValue[Item.ID] == Id.barnMinigameDot1:
                eventPlayer.tPos = 1
                barnMinigameClick